数据结构3(链表)

目录

一、单向链表

1、特点

2、链表的结构体

3、单项链表的构建

1)定义单个结点的结构体

2)构建结构体的组成部分,头结点和后继结点

3)头插

4)尾插

5)输出

6)头删

7)尾删

8)按位置插入

9)按位置删除

11)按位置查找

12)按值修改

13)逆置

二、单向循环列表

1、特点

2、单项循环链表的

3、关于单项循环链表的各项操作

1)定义链表结构体

2)构建单项循环列表

3)判空

4)头插

5)尾插

6)头删

7)尾删

8)输出

9)删除单项循环链表的头结点

10)删除头结点后的单项循环输出

三、双向链表

1、特点

2、结构

3、双向链表结构体构建

1)定义双向链表结构体

2)申请结点空间

3)判空

4)头插

4)尾插

5)按位置插入

6)头删

7)尾删

8)按位置删除

9)按值查找

10)按位置改

11)释放链表

四、双向循环链表

1、特点:

2、结构

3、双向循环链表的构建

1)定义双向循环链表结构体

2)申请结点空间

3)判空

4)头插

5)尾插

6)按位置插

7)头删

8)尾删

9)按位置删

10)输出

11)按值查找位置

12)按位置查找值


一、单向链表

逻辑结构:线性结构

存储结构:链式存储

1、特点

除最后一个结点外,每一个结点都有唯一的后继结点

2、链表的结构体

因为链表中每一个数据的地址是不连续的,所以需要申请多个结点的空间,每个结点包含存数据的数据域,以及存下一个结点地址的指针域

3、单项链表的构建

1)定义单个结点的结构体

由于链表的结构体是不连续的,需要存储下一个结点的首地址,

因此一个结点有数据和地址两个部分

cs 复制代码
typedef struct node
{
	union   //用共用体区分头结点和普通结点的数据域
	{
	    int data; //数据节点的数据域
	    int len;  //头结点的长度
	};
	struct node *next;
}node,*node_p;

2)构建结构体的组成部分,头结点和后继结点

cs 复制代码
//1、申请单链表(头结点)
node_p create_link()
{
 node_p H = (node_p)malloc(sizeof(node));
	if(H==NULL)
	{
	   return NULL;
	}
 H->next = NULL;
 H->len  = 0;  //当只有头结点时,长度为0
	return H;
}
//2、申请数据结点
node_p create_node(int value)
{
 node_p new_node = (node_p)malloc(sizeof(node));
	if(new_node==NULL)
	{
	 printf("申请空间失败\n");
	 return NULL;
	}
 new_node->data = value;  //给数据结点的数据域赋值
 new_node->next = NULL;   //数据节点的指针域赋值
	return new_node;
}

3)头插

插入在头结点的下一个位置,链表的第一个位置

所有从新结点出发的线都可以先连

cs 复制代码
//4、头插
void insert_head(node_p H,int value)
{
	if(H==NULL)
	{
		printf("入参为空,请检查\n");
		return;
	}
	//申请新结点
	node_p new = create_node(value);
	//让新结点指向原来头结点的后继结点
	new->next = H->next;
	//让头结点指向新结点
	H->next = new;
	//头结点保存的链表长度自增
	H->len++;
}

4)尾插

cs 复制代码
//5、尾插
void insert_tail(node_p H,int value)
{
	if(H==NULL)
	{
		printf("入参为空,请检查\n");
		return;
	}
	//1、找到尾结点
	node_p p = H;
	//尾结点的条件p->next==NULL;
	while(p->next!=NULL)
	{
		p = p->next;   //找下一个结点
	}
	//2、申请新的数据结点
	node_p new = create_node(value);
	//3、让最后一个结点的指针域指向新结点
	p->next = new;
	//4、因为新结点的指针域在申请后已经置NULL了,无需再次重置
	H->len++;
}

5)输出

cs 复制代码
//6、输出单向链表
void show_link(node_p H)
{
	if(H==NULL){return;}
	if(empty_link(H))
	{
		printf("链表为空,无需数超出\n");
		return;
	}
	//从第一个结点开始输出,
	//到最后一个结点结束,最后一个结点需要进入循环
	node_p p = H->next;
	while(p!=NULL)
	{
		printf("%d->",p->data);
		p = p->next;
	}
	//退出循环说明p走到了NULL,遍历结束整条链表
	printf("NULL\n");
}

6)头删

链表的删除需要释放堆区空间

cs 复制代码
//7、头删
void delete_head(node_p H)
{
	if(H==NULL){return;}
	if(empty_link(H)){return;}
	//1、保存要删除结点的首地址
	node_p dele = H->next;
	//2、让头结点指向原来的第二个结点
	H->next = H->next->next;  //H->next = dele->next;
	free(dele);
	H->len--;
}

7)尾删

尾删需要找到倒数第二个结点,因为单项链表只能向后访问

cs 复制代码
//8、尾删
void delete_tail(node_p H)
{
	if(H==NULL){return;}
	if(empty_link(H)){return;}
	//由于是单向链表只能向后查找
	//所以需要找到倒数第二个结点
	node_p p = H;
	while(p->next->next!=NULL)
	{
		p = p->next;
	}
	//退出循环说明找到倒数第二个结点
	node_p dele = p->next; //保存倒数第一个结点
	p->next = NULL;  //给倒数第二个结点的指针域置空
	free(dele);
	H->len--;
}

8)按位置插入

cs 复制代码
//9、按位置插入
void insert_pos(node_p H,int pos,int value)
{
	if(H==NULL){return;}
	//第一个循环变量i记录位置
	if(pos<=0)
	{
		printf("位置不合理\n");
		return;
	}
	int i = 0;
	//定义指针循环结点
	node_p p = H;
	for(i=0,p=H;i<pos-1;i++,p=p->next)
	{
		//判断位置不合理的情况
		if(p==NULL)
		{
			printf("插入位置不合理\n");
			return;
		}
	}
	node_p new = create_node(value);
	//新结点指向pos位置结点
	new->next = p->next;
	//pos-1位置结点,指向新结点
	p->next = new;
	H->len++;
}

9)按位置删除

cs 复制代码
//10、按位置删除
void dele_pos(node_p H,int pos)
{
	if(H==NULL){return;}
	if(pos<1)
	{
		printf("位置不合理\n");
		return;
	}
	int i;
	node_p p;
	//删除pos位置还是找到pos-1位置结点
	for(i=0,p=H;i<pos-1;i++,p=p->next);
	//找到pos-1位置结点后,判断是否有pos位置的结点
	if(p->next==NULL)
	{
		printf("位置不合理\n");
		return;
	}
	//保存要删除结点的首地址
	node_p dele = p->next;
	p->next = p->next->next; //p->next = dele->next;
	free(dele);
	H->len--;
}

11)按位置查找

cs 复制代码
//11、按位置查找返回元素的值
int search_pos(node_p H,int pos)
{
	if(H==NULL){return -2;}
	if(pos<1){return -1;}
	if(empty_link(H)){return -3;}
	int i;
	node_p p;
	for(i=1,p=H->next;i!=pos;i++,p=p->next)
	{
		if(p==NULL)
		{
			printf("位置不合理\n");
			return -1;
		}
	}
	return p->data;
}

12)按值修改

cs 复制代码
//12、按值修改
void update_value(node_p H,int value,int new_value)
{
	if(H==NULL){return;}
	if(empty_link(H)){return;}
	node_p p = H->next;
	//循环整条链表
	for(;p!=NULL;p=p->next)
	{
		if(p->data==value)
		{
			p->data = new_value;
			return;
		}
	}
}

13)逆置

cs 复制代码
//13、逆置
void reverse_link(node_p H)
{
	if(H==NULL){return;}
	if(empty_link(H)){return;}
	if(H->next->next==NULL){return;}
	//提前将第二个结点开始链表保存下来
	node_p p = H->next->next;
	node_p q;
	//让原来的第一个结点变成现在的最后一个结点
	H->next->next = NULL;
	//循环头插
	//只要p的指向的结点不为空说明要头插
	while(p!=NULL)
	{
		//先保留下一个要头插结点的首地址
		q = p->next;
		//头插
		p->next = H->next;
		H->next = p;
		p = q;  //让p指向下一个要头插的结点
	}
}

二、单向循环列表

1、特点

每个结点都有唯一的后继结点,尾结点的后继结点是头结点

2、单项循环链表的

3、关于单项循环链表的各项操作

1)定义链表结构体

cs 复制代码
typedef struct node
{
    union 
    {
        int len;
        int data;    
    };
    struct node *next;
}node,*node_p;

2)构建单项循环列表

cs 复制代码
//1、申请单向循环链表
node_p create_loop()
{
	node_p H = (node_p)malloc(sizeof(node));
	if(H==NULL)
	{
		printf("申请空间失败\n");
		return NULL;
	}
	H->len=0;
	H->next = H;
	return H;
}
//2、申请结点
node_p create_node(int value)
{
	node_p new = (node_p)malloc(sizeof(node));
	new->next = NULL;
	new->data = value;
	return new;
}

3)判空

cs 复制代码
//3、判空
int empty_loop(node_p H)
{
	if(H==NULL){return -1;}
	return H->next==H?1:0;
}

4)头插

cs 复制代码
//4、头插
void insert_head(node_p H,int value)
{
	if(H==NULL){return;}
	node_p new = create_node(value);
	//新结点的后继指向头结点的后继
	new->next = H->next;
	H->next = new;
	H->len++;
}

5)尾插

cs 复制代码
//5、尾插
void insert_tail(node_p H,int value)
{
	if(H==NULL){return;}
	node_p p=H; //p用于遍历链表,找到最后一个结点
	while(p->next!=H)
	{
		p=p->next;
	}
	node_p new = create_node(value);
	//退出循环后p指向最后一个结点
	p->next = new;
	new->next=H;
	//new->next = p->next;
	//p->next = new;
	H->len++;
}

6)头删

cs 复制代码
//6、头删
void dele_head(node_p H)
{
	if(H==NULL){return;}
	if(empty_loop(H)){return;}
	//保存要删除结点的首地址
	node_p p=H->next;
	//让头结点指向原来的第二个结点
	H->next=p->next;
	free(p);
	H->len--;
	return;
}

7)尾删

cs 复制代码
//7、尾删
void dele_tail(node_p H)
{
	if(H==NULL){return;}
	if(empty_loop(H)) {return;}
	node_p p=H;
	//尾删要找倒数第二个结点
	while(p->next->next!=H)
	{
		p=p->next;
	}
	//循环退出时p指向倒数第二个节点
	node_p q=p->next;
	p->next=H;
	free(q);
	//free(p->next);  //先释放最后一个结点
	//p->next = H;
	H->len--;
}

8)输出

cs 复制代码
//8、输出
void show_loop(node_p H)
{
	if(H==NULL){return;}
	if(empty_loop(H)){return;}
	node_p p = H->next;
	while(p!=H)
	{
		printf("%d->",p->data);
		p = p->next;
	}
	printf("H\n");
}

9)删除单项循环链表的头结点

cs 复制代码
//9、删除单向循环链表的头结点
//删除头结点后需要返回给主调函数处新的链表的头
node_p delete(node_p H)
{
	if(H==NULL){return NULL;}
	if(empty_loop(H)){return NULL;}
	//保存第一个结点的首地址
	node_p p = H->next;
	//找到最后一个结点
	node_p tail = H->next;
	while(tail->next!=H)
	{
		tail=tail->next;
	}
	//让最后一个结点的后继节点变成原来的第一个结点
	tail->next = p;
	//释放头结点
	free(H);
	return p;
}

10)删除头结点后的单项循环输出

原本的输出语句是不输出头结点的,删除头结点以后,原先的输出语句就不输出第一个结点了,

因此把while改成do{···}while,这样就会正常输出第一个结点了

cs 复制代码
//10、删除头结点后的单向循环链表的输出
void show_no_head(node_p H)
{
	if(H==NULL){return;}
	//不需要再判空
	//因为没有头结点了传过来的结点只要不是空说明链表中就有元素
	node_p p = H;
	do
	{
		printf("%d->",p->data);
		p = p->next;
	}while(p!=H);
	printf("H\n");
	//需要第一次循环时不判断条件
	//只能使用do··while
}

三、双向链表

1、特点

除头结点外,每个结点都有唯一的前驱

除尾结点外,每个结点都有唯一的后继

2、结构

逻辑结构:线性结构

存储结构:顺序结构

3、双向链表结构体构建

1)定义双向链表结构体

因为双向链表,可以双向访问,那么就要存两个地址,前驱的地址和后继的地址。

cs 复制代码
typedef struct node
{
    union
    {
        int len;
        int data;    
    };
    struct node *pri;     //指向前驱结点的指针
    struct node *next;    //指向后继结点的指针
}node,*node_p;

2)申请结点空间

cs 复制代码
//1、创建双向链表
node_p create_double()
{
	node_p H=(node_p)malloc(sizeof(node));
	if(H==NULL)
	{
		return NULL;
	}
	H->len=0;
	H->next=NULL;
	H->pri=NULL;
	return H;
}
//2、创建结点
node_p create_node(int data)
{
	node_p new = (node_p)malloc(sizeof(node));
	if(new==NULL){return NULL;}
	new->data = data;
	new->pri = NULL;
	new->next = NULL;
	return new;
}

3)判空

cs 复制代码
//3、判空
int empty_double(node_p H)
{
	if(H==NULL){return -1;}
	return H->next==NULL;
}

4)头插

cs 复制代码
//4、头插
void insert_head(node_p H,int value)
{
	if(H==NULL){return;}
	node_p new = create_node(value);
	//新结点的后继保存原来头结点的后继
	new->next = H->next;
	//新结点的前驱指向头结点
	new->pri = H;
	//头结点后继节点的前驱指向新结点
	if(H->next!=NULL)
	{
		H->next->pri = new;
	}
	//头结点的后继指向新结点
	H->next = new;
	H->len++;
}

4)尾插

cs 复制代码
//5、尾插
void insert_tail(node_p H,int value)
{
	if(H==NULL)
	{return;}
	node_p p = H;
	while(p->next!=NULL)
	{
		p = p->next;
	}
	node_p new = create_node(value);
	//新结点的前驱指向尾结点
	new->pri = p;
	//尾结点的后继指向新结点
	p->next = new;
	H->len++;
}

5)按位置插入

6)头删

cs 复制代码
//8、头删
void dele_head(node_p H)
{
	if(H==NULL){return;}
	if(empty_double(H)){return;}
	//保存要删除结点的首地址
	node_p del = H->next;
	//如果链表中节点个数多于一
	if(del->next!=NULL)
	{
		del->next->pri = H;
	}
	//让头结点的后继保存第一个结点的后继
	H->next = del->next;
	free(del);
	H->len--;
}

7)尾删

cs 复制代码
//9、尾删
void dele_tail(node_p H)
{
	if(H==NULL){return;}
	if(empty_double(H)){return;}
	//找到最后一个结点
	node_p p = H;
	while(p->next!=NULL)
	{
		p = p->next;
	}
	//退出循环时p是最后一个结点
	//让倒数第二个结点的后继指向NULL
	p->pri->next = NULL;
	free(p);
	H->len--;
}

8)按位置删除

cs 复制代码
//10、按位置删除
void dele_pos(node_p H,int pos)
{
	if(H==NULL){return;}
	if(empty_double(H)){return;}
	if(pos<=0){return;}
	//找到pos位置的结点
	int i;node_p p;
	for(i=0,p=H;i<pos;i++,p=p->next)
	{
		if(p==NULL)
		{
			printf("位置不合理\n");
			return;
		}
	}
	//循环结束p指向pos位置的结点
	if(p==NULL)
	{
		printf("位置不合理\n");
		return;
	}
	//pos+1结点的前驱指向pos-1结点 
	if(p->next!=NULL)
	{
		p->next->pri = p->pri;
	}
	//pos-1结点的后继指向pos+1结点 
	p->pri->next = p->next;
	free(p);
	H->len++;
}

9)按值查找

cs 复制代码
//11、按值查找返回位置
int search_value(node_p H,int value)
{
	if(H==NULL)
	{return -1;}
	if(empty_double(H)){return -2;}
}

10)按位置改

cs 复制代码
//12、按位置修改元素
void update_pos(node_p H,int pos,int new_value)
{
	if(H==NULL){return;}
	if(empty_double(H)){return;}
	if(pos<=0){return;}
}

11)释放链表

cs 复制代码
//13、释放双向循环链表
void des_double(node_p *H)
{
	if(H==NULL||*H==NULL)
	{return;}
	while((*H)->next)
	{
		dele_head(*H);
	}
	//退出循环说明链表中只有头结点了
	free(*H);
	*H=NULL;
}

四、双向循环链表

1、特点:

每个结点都有唯一的前驱,头结点的前驱是尾结点

每个结点都有唯一的后继,尾结点的后继是头结点

2、结构

3、双向循环链表的构建

1)定义双向循环链表结构体

cs 复制代码
typedef struct node
{
    union
    {
        int len;
        int data;
    };                   
    struct node *pri;
    struct node *next;
}node,*node_p;

2)申请结点空间

cs 复制代码
//创建头结点
node_p create_head()
{
    node_p H=(node_p)malloc(sizeof(node));
    if(H==NULL) {return NULL;}
    H->pri=H;
    H->len=0;
    H->next=H;
    return H;
}

//创建结点
node_p create_node(int value)
{
    node_p new=(node_p)malloc(sizeof(node));
    if(new==NULL) {return NULL;}
    new->pri=NULL;
    new->data=value;
    new->next=NULL;
    return new;
}

3)判空

cs 复制代码
//判空
int empty_double(node_p H)      
{
    if(H==NULL) return -2;
    return H->next==H;
}

4)头插

cs 复制代码
//头插
void insert_head(node_p H,int value)
{
    if(H==NULL) {return;}
    node_p new=create_node(value);
    
    new->pri=H;
    new->next=H->next;
    H->next=new;
    new->next->pri=new;

    H->len++;
}

5)尾插

cs 复制代码
//尾插
void insert_tail(node_p H,int value)
{
    if(H==NULL) {return;}
    node_p new=create_node(value);
    node_p p=H;
    while(p->next!=H) {p=p->next;}
    p->next=new;
    new->pri=p;
    new->next=H;
    H->pri=new;
    H->len++;
}

6)按位置插

cs 复制代码
//按位置插
void insert_pos(node_p H,int pos,int value)
{
    if(H==NULL) {return;}
    node_p new=create_node(value);
    node_p p=H;
    for(int i=0;i<pos-1;i++,p=p->next)
    {
        if(p->next==H) {return;}
    }
    new->next=p->next;
    new->pri=p;
    p->next=new;
    new->next->pri=new;
    H->len++;

7)头删

8)尾删

9)按位置删

10)输出

cs 复制代码
//输出
void show(node_p H)
{
    if(H==NULL) {return;}
    if(empty_double(H)) {return;}
    node_p p=H->next;
    for(;p!=H;p=p->next)
    {
        printf("%d->",p->data);
    }
    putchar(10);
}

11)按值查找位置

12)按位置查找值

相关推荐
码破苍穹ovo1 小时前
二分查找----1.搜索插入位置
数据结构·算法
liujing102329293 小时前
Day05_数据结构大项目作业20250620
数据结构
int型码农3 小时前
数据结构第八章(六)-置换选择排序和最佳归并树
java·c语言·数据结构·算法·排序算法
@我漫长的孤独流浪4 小时前
数据结构----排序(3)
数据结构·c++·算法
YuTaoShao5 小时前
Java八股文——数据结构「数据结构篇」
java·数据结构·面试·八股文
-qOVOp-6 小时前
408第二季 - 组成原理 - 流水线
数据结构·算法
zylyyyyyy9 小时前
Polar (极化)码的译码Ⅱ--BP 译码算法
数据结构·算法
liujing1023292910 小时前
Day05_数据结构(二叉树&快速排序&插入排序&二分查找)
数据结构
YuTaoShao10 小时前
Java八股文——数据结构「排序算法篇」
java·数据结构·算法·面试·排序算法·八股文