数据结构和算法三:链表(重要)

1. 链表是什么?

  • 顺序表的缺点
    1. 添加和删除操作需要移动元素。
    2. 当数据量特别大的情况,可能没有连续的内存可使用。

链表,别名链式存储结构或单链表,用于存储逻辑关系为 “一对一” 的数据。与顺序表不同,链表不限制数据的物理存储状态。顺序表通过连续的地址建立元素之间前后连接关系,链表通过指针方式建立元素之间前后连接关系。

2. 链表怎么用?

链表用法与顺序表相似,只是适用场景有所不同。

3. 链表如何实现

3.1 定义结构

使用链表存储的数据元素,其物理存储位置是随机的。数据元素随机存储,并通过指针表示数据之间逻辑关系的存储结构就是链式存储结构。

链表中每个数据的存储都由以下两部分组成:

  1. 数据元素本身,其所在的区域称为数据域;
  2. 指向直接后继元素的指针,所在的区域称为指针域;

单链表与字符串有很多相似之处:单链表的结尾为NULL,字符串的结尾为\0。所以,二者处理有许多相似之处。

注意:创建的节点node(1)存放数据和next指针域;
node(2) :用来有特殊用途;

List表示最后的结果;

typedef int LinkType;

typedef struct _Node{
	struct _Node* next; // 指针域
	LinkType val; // 数据域
}Node;

typedef struct {
	Node* head;// 头指针
	Node* tail;// 尾指针
	int size;
} List;

定义一个存储单元类型LinkType是为了使顺序表适和更多数据类型,使用的时候修改LinkType类型即可。

3.2 定义操作

对比 序号
1-2-3 创建–初始化–销毁
4-5 插入
1-2-3
1-2-3
  1. 创建链表
     // 创建
List list_create(){
	List l = {NULL,NULL,0};
	return l;
}
  1. 初始化
    (1)首尾指针为空;
    (2)size为空;
// 初始化
bool list_init(List* seq){
	seq->head = NULL;
	seq->tail = NULL;
	seq->size = 0;
	return true;
}
  1. 销毁链表
    (1)释放首尾指针;
    (2)首尾指针为空;
    (3)size为空;
    // 销毁
void list_destroy(List* seq){
    free(seq->head);
    free(seq->tail);
    seq->head = NULL;
	seq->tail = NULL;
	seq->size = 0;
}
  1. 添加元素
    (4.1)尾部添加关键:找到最后一个节点)
    (4.1.1)循环的方案:
    在这里插入图片描述
void list_append(List* seq,LinkType val){
	// 创建新节点
	Node* n = malloc(sizeof(Node));
	n->val = val;
	n->next = NULL;
	if(NULL == seq->head){ // 第一个节点
		seq->head = n;
		seq->tail = n;
	}else{
		// 找到最后一个节点
		Node* p = seq->head;
		while(NULL != p->next){
			p = p->next;
		}
		p->next = n;
		seq->tail = n;
		
	}
	seq->size++;
}

(4.1.2)通用的方案:
在这里插入图片描述

void list_append(List* seq,LinkType val){
	// 创建新节点
	Node* n = malloc(sizeof(Node));
	n->val = val;
	n->next = NULL;
	if(NULL == seq->head){ // 第一个节点
		seq->head = n;
		seq->tail = n;
	}else{
		seq->tail->next = n;
		seq->tail = n;
	}
	seq->size++;
}

(4.2)头部添加关键:找到头结点)
4.2.1 n->next表示头指针
在这里插入图片描述

void list_prepend(List* seq,LinkType val){
	// 创建新节点
	Node* n = malloc(sizeof(Node));
	n->val = val;
	n->next = seq->head;
	if(NULL == seq->head){
		seq->tail = n;
	}
	seq->head = n;
	seq->size++;

}

4.2.2 n表示尾指针
在这里插入图片描述

void list_prepend(List* seq,LinkType val){
	// 创建新节点
	Node* n = malloc(sizeof(Node));
	n->val = val;
	n->next = NULL;
	if(NULL == seq->head){ // 第一个节点
		seq->head = n;
	}else{
		n->next = seq->head;
		seq->head = n;
	}
	seq->size++;
}
  1. 插入元素

在这里插入图片描述

// 插入数据
void list_insert(List* seq,int index,LinkType val){
	// 创建新节点
	Node* n = malloc(sizeof(Node));
	n->val = val;
	n->next = NULL;

	// 在头部插入
	if(NULL == seq->head || 0 == index){
		list_prepend(seq,val);
		return;
	}

	// for(Node* p=seq->head;NULL!=p;p=p->next){}
	// 找到前一个节点
	Node* p = seq->head;
	int count = 0;
	while(NULL!= p){
		if(index-1 == count) break;
		++count;
		p = p->next;
	}
	
 
	// 在尾部插入
	if(NULL == p){
		list_append(seq,val);
		return;
	}
	// 中间部插入
	Node* q = p->next;
	p->next = n;
	n->next = q;
	seq->size++;
}
  1. 删除元素
    在这里插入图片描述
  // 删除数据
void list_delete(List* seq,int index){
	// 删除第一个节点
	if(0 == index){
		Node* del = seq->head;
		seq->head =   del->next;
		free(del);
		seq->size--;

		if(0==seq->size){// 删除最后一个节点
			seq->head = seq->tail = NULL;
		}
		return;
	}
	// 找到前一个节点
	Node* p = seq->head;
	int count = 0;
	while(NULL!= p){
		if(index-1 == count) break;
		++count;
		p = p->next;
	}

	if(NULL == p){// index不在删除范围
		return;
	}

	// 删除其他节点
	Node* del = p->next;
	if(NULL == del->next){// 删除尾节点
		seq->tail = p;
	}
	p->next = del->next;
	free(del);
	seq->size--;
}
       
  1. 获取元素

    用index和循环的次数进行对比;
    if(index == count) return &(p->val);

       LinkType* list_at(List* seq,int index){
	int count = 0;
	Node* p = seq->head;
	while(NULL!=p){
		if(index == count) return &(p->val);
		++count;
		p = p->next;
	}
	return NULL;
}
  1. 返回元素的个数
// 获取元素个数
int list_size(List* seq){
	/*
	int size = 0;
	Node* p = seq->head;
	while(NULL!=p){
		++size;
		p = p->next;
	}
	*/
	return seq->size;
}
  1. 实例代码
联系起来的方式
//链表表结构体
typedef struct{
	char name[20];
	bool sex;
	int age;
} Student;

typedef Student LinkType;

typedef struct _Node{
	struct _Node* next; // 指针域
	LinkType val; // 数据域
}Node;

typedef struct {
	Node* head;// 头指针
	Node* tail;// 尾指针
	int size;
} List; 

(1)main.c

#include <stdio.h>
#include "LinkList.h"

void PrintElement(LinkType* val){
	printf("%d ",*val);
}

int main(){
	List l = list_create();

	list_append(&l,1);
	list_print(&l);

	list_append(&l,2);
	list_print(&l);

	list_append(&l,3);
	list_print(&l);

	list_append(&l,4);
	list_print(&l);

	list_append(&l,5);
	list_print(&l);

	list_prepend(&l,10);
	list_print(&l);
	list_prepend(&l,11);
	list_print(&l);
	list_prepend(&l,12);
	list_print(&l);
	list_prepend(&l,13);
	list_print(&l);

	list_insert(&l,0,0);
	list_print(&l);
	list_insert(&l,100,0);
	list_print(&l);
	list_insert(&l,3,0);
	list_print(&l);

	list_delete(&l,3);
	list_print(&l);
	list_delete(&l,0);
	list_print(&l);
	list_delete(&l,9);
	list_print(&l);
	printf("size=%d\n",list_size(&l));

	LinkType* p = list_at(&l,5);
	*p = 500;
	list_print(&l);
	list_traval(&l,PrintElement);

	list_destroy(&l);
}

(2)LinkList.c

#include "LinkList.h"
#include <stdio.h>
#include <stdlib.h>

// 创建
List list_create(){
	List l = {NULL,NULL,0};
	return l;
}
// 初始化
bool list_init(List* seq){
	seq->head = NULL;
	seq->tail = NULL;
	seq->size = 0;
	return true;
}
// 销毁
void list_destroy(List* seq){
    free(seq->head);
    free(seq->tail);
    seq->head = NULL;
	seq->tail = NULL;
	seq->size = 0;
}

// 基本操作
// 添加数据
void list_append(List* seq,LinkType val){
	// 创建新节点
	Node* n = malloc(sizeof(Node));
	n->val = val;
	n->next = NULL;
	if(NULL == seq->head) { // 第一个节点
		seq->head = n;
		seq->tail = n;
	}else{
		// 找到最后一个节点
		/*
		Node* p = seq->head;
		while(NULL != p->next){
			p = p->next;
		} 
		p->next = n;
		*/
		seq->tail->next = n;
		seq->tail = n;
	}
	seq->size++;
}
void list_prepend(List* seq,LinkType val){
	// 创建新节点
	Node* n = malloc(sizeof(Node));
	n->val = val;
	n->next = seq->head;
	if(NULL == seq->head){
		seq->tail = n;
	}
	seq->head = n;
	seq->size++;
/*
	n->next = NULL;
	if(NULL == seq->head){ // 第一个节点
		seq->head = n;
	}else{
		n->next = seq->head;
		seq->head = n;
	}
	*/
}
// 插入数据
void list_insert(List* seq,int index,LinkType val){
	// 创建新节点
	Node* n = malloc(sizeof(Node));
	n->val = val;
	n->next = NULL;

	// 在头部插入
	if(NULL == seq->head || 0 == index){
		list_prepend(seq,val);
		return;
	}

	// for(Node* p=seq->head;NULL!=p;p=p->next){}
	// 找到前一个节点
	Node* p = seq->head;
	int count = 0;
	while(NULL!= p){
		if(index-1 == count) break;
		++count;
		p = p->next;
	}
	
 
	// 在尾部插入
	if(NULL == p){
		list_append(seq,val);
		return;
	}
	// 中间部插入
	Node* q = p->next;
	p->next = n;
	n->next = q;
	seq->size++;
}
// 删除数据
void list_delete(List* seq,int index){
	// 删除第一个节点
	if(0 == index){
		Node* del = seq->head;
		seq->head =   del->next;
		free(del);
		seq->size--;

		if(0==seq->size){// 删除最后一个节点
			seq->head = seq->tail = NULL;
		}
		return;
	}
	// 找到前一个节点
	Node* p = seq->head;
	int count = 0;
	while(NULL!= p){
		if(index-1 == count) break;
		++count;
		p = p->next;
	}

	if(NULL == p){// index不在删除范围
		return;
	}

	// 删除其他节点
	Node* del = p->next;
	if(NULL == del->next){// 删除尾节点
		seq->tail = p;
	}
	p->next = del->next;
	free(del);
	seq->size--;
}
// 获取数据
LinkType list_get(List* seq,int index);
LinkType* list_at(List* seq,int index){
	int count = 0;
	Node* p = seq->head;
	while(NULL!=p){
		if(index == count) return &(p->val);
		++count;
		p = p->next;
	}
	return NULL;
}
// 获取元素个数
int list_size(List* seq){
	/*
	int size = 0;
	Node* p = seq->head;
	while(NULL!=p){
		++size;
		p = p->next;
	}
	*/
	return seq->size;
}

// 打印
void list_print(List* seq){
	Node* p = seq->head;
	while(NULL != p){
		printf("%d ",p->val);
		p = p->next;
	}
	printf("\n");
}
// 遍历
typedef void (*traval_t)(LinkType* val);
void list_traval(List* seq,traval_t func){
	Node* p = seq->head;
	while(NULL != p){
		(*func)(&(p->val));
		p=p->next;
	}
}

// int list_comp_element(const LinkType* a,const LinkType* b)

void list_sort(List* seq);

// 清空
void list_clear(List* seq);
// 判空
bool list_empty(List* seq);
// 查找
int list_find(List* seq,LinkType val);
LinkType* list_search(List* seq,LinkType val);

(3)LinkList.h

  #ifndef _LINKLIST_H
#define _LINKLIST_H
#include <stdbool.h>

// 顺序表结构体
typedef struct{
	char name[20];
	bool sex;
	int age;
} Student;

typedef Student LinkType;

typedef struct _Node{
	struct _Node* next; // 指针域
	LinkType val; // 数据域
}Node;

typedef struct {
	Node* head;// 头指针
	Node* tail;// 尾指针
	int size;
} List;

// 创建
List list_create();
// 初始化
bool list_init(List* seq);
// 销毁
void list_destroy(List* seq);

// 基本操作
// 添加数据
void list_append(List* seq,LinkType val);
void list_prepend(List* seq,LinkType val);
// 插入数据
void list_insert(List* seq,int index,LinkType val);
// 删除数据
void list_delete(List* seq,int index);
// 获取数据
LinkType list_get(List* seq,int index);
LinkType* list_at(List* seq,int index);
// 获取元素个数
int list_size(List* seq);

// 打印
void list_print(List* seq);
// 遍历
typedef void (*traval_t)(LinkType* val);
void list_traval(List* seq,traval_t func);

// int list_comp_element(const LinkType* a,const LinkType* b)

void list_sort(List* seq);

// 清空
void list_clear(List* seq);
// 判空
bool list_empty(List* seq);
// 查找
int list_find(List* seq,LinkType val);
LinkType* list_search(List* seq,LinkType val);

#endif // _LINKLIST_H

4. 优化

  1. 创建
    用户使用LinkType忘记初始化,可以把结构体定义和初始化合二为一。
    (即把创建和初始化放在LinkList.h文件里面);

亚元(单节点的表示)

      // 创建
List list_create(){
	Node* dummy = malloc(sizeof(Node));// 头结点
	dummy->next = NULL;
	List l = {dummy,dummy,0};
	return l;
}
  1. 随机访问元素
    获取元素linklist_get(),只能获取到顺序表中的元素的副本,如果需要改变链表中的元素,可以提供如下函数。
  // 获取数据
LinkType list_get(List* seq,int index);
LinkType* list_at(List* seq,int index){
	int count = -1;
	Node* p = seq->head;
	while(NULL!=p){
		if(index == count) return &(p->val);
		++count;
		p = p->next;
	}
	return NULL;
}
  1. 遍历
    提供一个对链表的整体操作的接口。
    // 遍历
typedef void (*traval_t)(LinkType* val);
void list_traval(List* seq,traval_t func){
	Node* p = seq->head->next;
	while(NULL != p){
		(*func)(&(p->val));
		p=p->next;
	}
}

在这里插入图片描述
完整代码
①main.c(同上)

②LinkList.c

#include "LinkList.h"
#include <stdio.h>
#include <stdlib.h>

// 创建
List list_create(){
	Node* dummy = malloc(sizeof(Node));// 头结点
	dummy->next = NULL;
	List l = {dummy,dummy,0};
	return l;
}
// 初始化
bool list_init(List* seq){
	Node* dummy = malloc(sizeof(Node));// 头结点
	dummy->next = NULL;
	seq->head = dummy;
	seq->tail = dummy;
	seq->size = 0;
	return true;
}
// 销毁
void list_destroy(List* seq){
	Node* p = seq->head;
	while(NULL != p){
		Node* del =p;
		p = p->next;
		free(del);
	}
	seq->size = 0;
	seq->head = seq->tail = NULL;
}

// 基本操作
// 添加数据
void list_append(List* seq,LinkType val){
	// 创建新节点
	Node* n = malloc(sizeof(Node));
	n->val = val;
	n->next = NULL;
	seq->tail->next = n;
	seq->tail = n;
	seq->size++;
}
void list_prepend(List* seq,LinkType val){
	// 创建新节点
	Node* n = malloc(sizeof(Node));
	n->val = val;
	n->next = seq->head->next;
	if(seq->tail == seq->head){
		seq->tail = n;
	}
	seq->head->next = n;
	seq->size++;
}
// 插入数据
void list_insert(List* seq,int index,LinkType val){
	// 创建新节点
	Node* n = malloc(sizeof(Node));
	n->val = val;
	n->next = NULL;

	// for(Node* p=seq->head;NULL!=p;p=p->next){}
	// 找到前一个节点
	Node* p = seq->head;
	int count = -1;
	while(NULL!= p){
		if(index-1 == count) break;
		++count;
		p = p->next;
	}

	// 在尾部插入
	if(NULL == p){
		list_append(seq,val);
		return;
	}
	// 中间部插入
	Node* q = p->next;
	p->next = n;
	n->next = q;
	seq->size++;
}
// 删除数据
void list_delete(List* seq,int index){
	// 找到前一个节点
	Node* p = seq->head;
	int count = -1;
	while(NULL!= p){
		if(index-1 == count) break;
		++count;
		p = p->next;
	}

	if(NULL == p){// index不在删除范围
		return;
	}

	// 删除其他节点
	Node* del = p->next;
	if(NULL == del->next){// 删除尾节点
		seq->tail = p;
	}
	p->next = del->next;
	free(del);
	seq->size--;
}
// 获取数据
LinkType list_get(List* seq,int index);
LinkType* list_at(List* seq,int index){
	int count = -1;
	Node* p = seq->head;
	while(NULL!=p){
		if(index == count) return &(p->val);
		++count;
		p = p->next;
	}
	return NULL;
}
// 获取元素个数
int list_size(List* seq){
	/*
	int size = 0;
	Node* p = seq->head;
	while(NULL!=p){
		++size;
		p = p->next;
	}
	*/
	return seq->size;
}

// 打印
void list_print(List* seq){
	Node* p = seq->head->next;
	while(NULL != p){
		printf("%d ",p->val);
		p = p->next;
	}
	printf("\n");
}
// 遍历
typedef void (*traval_t)(LinkType* val);
void list_traval(List* seq,traval_t func){
	Node* p = seq->head->next;
	while(NULL != p){
		(*func)(&(p->val));
		p=p->next;
	}
}

// int list_comp_element(const LinkType* a,const LinkType* b)

void list_sort(List* seq);

// 清空
void list_clear(List* seq);
// 判空
bool list_empty(List* seq);
// 查找
int list_find(List* seq,LinkType val);
LinkType* list_search(List* seq,LinkType val); 

③LinkList.h(同上)

5. 插入和删除操作分析

5.1 插入

  1. 开头插入新节点

    在这里插入图片描述

  2. 末尾插入新节点
    在这里插入图片描述

5.2 删除

链表指针两大操作:

  1. 修改指向节点:p = ?

  2. 修改节点连接:p->next = ?

  3. 开头删除节点
    在这里插入图片描述

  4. 末尾删除节点
    在这里插入图片描述

  5. 中间删除节点

    在这里插入图片描述

注意:
存在两种特殊情况需要处理

  1. 空链表删除节点
    此情况使用断言或者抛出异常。
  2. 待删除节点的链表中只有一个节点
    删除后,需要把头指针和尾指针设置为空

6. 练习

  1. 实现一个清空接口
  2. 实现一个判空接口
  3. 实现一个查找指定元素的接口,返回元素下标
  4. 实现一个查找指定元素的接口,返回元素地址
  5. 实现比较两个元素的接口
  6. 实现交换两个元素的接口
    Leetcode206. 反转链表
  • 静态链表
    静态链表相当于用一个数组来实现线性表的链式存储结构,使用下标代替指针。主要被用于没有指针的计算机语言。FAT文件系统是使用静态链表实现的。

6. 其他链表

在这里插入图片描述

6.1 循环单链表

单链表有一个特点只能从头指针开始遍历整个链表,循环单链表可以从任意节点开始遍历整个链表。

在这里插入图片描述

6.3.1 前端插入节点

在这里插入图片描述

6.3.2 末尾插入节点

在这里插入图片描述

6.2 双向链表

在这里插入图片描述

单链表在末尾删除节点时,需要获得删除节点前面的一个节点。这需要从队列开头一直遍历到结尾,导致效率瓶颈。双向链表很好的解决这个问题。

6.2.1 末尾插入新节点

在这里插入图片描述

6.2.2 末尾删除节点

在这里插入图片描述

注意:
存在两种特殊情况需要处理

  1. 空链表删除节点
    此情况使用断言或者抛出异常。
  2. 待删除节点的链表中只有一个节点
    删除后,需要把头指针和尾指针设置为空

7. 练习

  1. 单向链表-七牛云2018秋
  2. 序列操作-美团点评2018秋
发布了33 篇原创文章 · 获赞 8 · 访问量 2265

猜你喜欢

转载自blog.csdn.net/Pierce110110/article/details/104168973
今日推荐