数据结构:动态链表(C语言描述)

这一篇文章我们先简单的说一下动态链表。

下一篇静态链表文章链接:https://blog.csdn.net/weixin_41746479/article/details/118498423?spm=1001.2014.3001.5502

主要内容:普通单链表、单向循环链表和双向循环链表、如何判断链表中是否包含环和入环点的确定、链表的合并,以及一些循环链表经典题目。

①普通单链表:

这里我只是简单的定义了一个长度为10的单链表并输出相关内容,并没有写创建、插入、删除、查找等相关功能。对链表熟悉了之后就会发现创建链表的头插法和尾插法只是两个模板而已。下面的代码创建链表的方法就十分简单。

普通单链表不从头节点开始遍历就无法遍历完全部的节点。这在某些情况下具有很大的弊端。

#include
#include
typedef struct List{//定义结构体	
	int number;
	struct List *next;
}L;

int main()
{	
	L *p=(L*)malloc(sizeof(struct List));//需要进行处理的节点
	p->next=(L*)malloc(sizeof(struct List));//给下一个节点分配内存
	p->number=0;//第一个节点数据域赋值 
	L *head=p;//头节点保留初始节点地址 
	
	for(int i=1;i<10;i++){//创建一个长度为10的链表,头节点带数据这里只需要进行9次循环即可 
		p=p->next;//指向下一个节点	
		p->number=i;//赋值 
		p->next=(L*)malloc(sizeof(struct List));//分配内存 
	}
	p->next=NULL;
	
	while(head){//输出数据 
		printf("%d  ",head->number);
		head=head->next;
	}
//输出结果:0  1  2  3  4  5  6  7  8  9		
} 

②:单向循环链表:

顾名思义,单向:朝着一个方向移动节点。循环:形成了一个环,可以循环输出。

循环链表也很简单,就只需要将上面写的代码进行一点更改,让最后一个节点指向的下一个节点不指向NULL,而是指向头节点。这样就形成了一个环。

循环链表判断为空的条件:当前节点指向下一个节点的指针是否等于当前节点。

#include
#include
typedef struct List{//定义结构体	
	int number;
	struct List *next;
}L;

int main()
{	
	L *p=(L*)malloc(sizeof(struct List));//需要进行处理的节点
	p->next=(L*)malloc(sizeof(struct List));//给下一个节点分配内存
	p->number=0;//第一个节点数据域赋值 
	L *head=p;//头节点保留初始节点地址 
	
	for(int i=1;i<10;i++){//创建一个长度为10的链表,头节点带数据这里只需要进行9次循环即可 
		p=p->next;//指向下一个节点	
		p->number=i;//赋值 
		p->next=(L*)malloc(sizeof(struct List));//分配内存 
	
	}
	//指向头节点形成循环链表 
	p->next=head;
	
	int count=20;
    //这里如果不设置次数的话就陷入死循环
	while(count--){//输出数据 
		printf("%d  ",head->number);
		head=head->next;
	}
//输出结果:0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5  6  7  8  9		
} 

③:双向循环链表:

顾名思义,双向:两个方向都可以移动节点。循环:形成了一个环,可以循环输出。

双向循环链表只需要在单向循环链表的基础上加一个指向当前节点前一个节点的指针即可。直接看代码。

#include
#include
typedef struct List{//定义结构体	
	int number;
	struct List *next;
	struct List *previous;//指向前一个节点的指针。 
}L;

int main()
{	
	L *p=(L*)malloc(sizeof(struct List));//需要进行处理的节点
	p->next=(L*)malloc(sizeof(struct List));//给下一个节点分配内存
	p->previous=(L*)malloc(sizeof(struct List));//当前节点指向上一个节点的指针分配内存 
	p->next->previous=(L*)malloc(sizeof(struct List));//给下一个节点指向上一个节点的指针分配内存
	p->next->previous=p;//下一个节点指向上一个节点的指针指向当前节点 
	p->number=0;//第一个节点数据域赋值 
	L *head=p;//头节点保留初始节点地址 
	
	for(int i=1;i<10;i++){//创建一个长度为10的链表,头节点带数据这里只需要进行9次循环即可 
		p=p->next;//指向下一个节点
		p->number=i;//赋值
		p->next=(L*)malloc(sizeof(struct List));//分配内存 
		p->next->previous=(L*)malloc(sizeof(struct List));
		p->next->previous=p;
	}
	 
	p->next=head;//尾节点指向下一个节点的指针指向头节点 
	head->previous=p;//头节点指向上一个节点的指针指向尾节点 

	int count=20;
    //这里如果不设置次数的话就陷入死循环
	while(count--){//正向输出数据 
		printf("%d  ",head->number);
		head=head->next;
	}
	printf("\n");
	count=20;
	while(count--){//逆向输出数据 
		printf("%d  ",p->number);
		p=p->previous;
	}
//输出结果: 0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5  6  7  8  9
//          9  8  7  6  5  4  3  2  1  0  9  8  7  6  5  4  3  2  1  0		
} 

④:判断链表当中是否有环以及入环点

下面这篇博客就讲的很清楚,思路最简单的还是穷举遍历和快慢指针两种方法,通俗易懂。

https://blog.csdn.net/u010983881/article/details/78896293

⑤:链表的合并

普通单链表的合并:A链表尾节点的next指针指向B链表的头节点。

Aend->next=Bhead;

单向循环链表的合并:A循环链表尾节点的next指针指向B链表的头节点之后,B循环链表的尾节点的next指针指向A链表的头节点。

Aend->next=Bhead;
Bend->next=Ahead;

双向循环链表的合并:在单向循环链表合并的基础上将A循环链表的previous指针指向B循环链表的尾节点,然后将B循环链表头节点的previous指针指向A节点的尾节点。

Aend->next=Bhead;
Bend->next=Ahead;
Ahead->previous=Bend;
Bhead->previous=Aend;

⑥:光说不练假把式

一.约瑟夫环问题

已知 n 个人(以编号 1,2,3,…,n 分别表示)围坐在一张圆桌周围,从编号为 k 的人开始顺时针报数,数到 m 的那个人出列;他的下一个人又从 1 还是顺时针开始报数,数到 m 的那个人又出列;依次重复下去,要求找到最后出列的那个人?

例如有 5 个人,要求从编号为 3 的人开始,数到 2 的那个人出列:

数据结构:动态链表(C语言描述)_第1张图片

出列顺序依次为:
编号为 3 的人开始数 1,然后 4 数 2,所以 4 先出列;
4 出列后,从 5 开始数 1,1 数 2,所以 1 出列;
1 出列后,从 2 开始数 1,3 数 2,所以 3 出列;
3 出列后,从 5 开始数 1,2 数 2,所以 2 出列;
最后只剩下 5 自己,所以 5 出列。

二.魔术师发牌问题

一位魔术师掏出一叠扑克牌,魔术师取出其中13张黑桃,洗好后,把牌面朝下。
说:“我不看牌,只数一数就能知道每张牌是什么?”魔术师口中念一,将第一张牌翻过来看正好是A;
魔术师将黑桃A放到桌上,继续数手里的余牌,
第二次数1,2,将第一张牌放到这叠牌的下面,将第二张牌翻开,正好是黑桃2,也把它放在桌子上。
第三次数1,2,3,前面二张牌放到这叠牌的下面,取出第三张牌,正好是黑桃3,这样依次将13张牌翻出,全部都准确无误。
求解:魔术师手中牌的原始顺序是什么样子的?
 

自行设计代码实现上述功能。

你可能感兴趣的:(数据结构,数据结构,算法,链表,指针)