链表和数组作为算法中的两个基本数据结构,在程序设计过程中经常用到。尽管两种结构都可以用来存储一系列的数据,但又各有各的特点。
数组的优势,在于可以方便的遍历查找需要的数据。在查询数组指定位置(如查询数组中的第4个数据)的操作中,只需要进行1次操作即可,时间复杂度为O(1)。但是,这种时间上的便利性,是因为数组在内存中占用了连续的空间,在进行类似的查找或者遍历时,本质是指针在内存中的定向偏移。然而,当需要对数组成员进行添加和删除的操作时,数组内完成这类操作的时间复杂度则变成了O(n)。
链表的特性,使其在某些操作上比数组更加高效。例如当进行插入和删除操作时,链表操作的时间复杂度仅为O(1)。另外,因为链表在内存中不是连续存储的,所以可以充分利用内存中的碎片空间。除此之外,链表还是很多算法的基础,最常见的哈希表就是基于链表来实现的。基于以上原因,我们可以看到,链表在程序设计过程中是非常重要的。本文总结了我们在学习链表的过程中碰到的问题和体会。
接下来,我们将对链表进行介绍,用C语言分别实现:链表的初始化、创建、元素的插入和删除、链表的遍历、元素的查询、链表的删除、链表的逆序以及判断链表是否有环等这些常用操作。并附上在Visual Studio 2013中可以运行的代码供学习者参考。
说到链表,可能有些人还对其概念不是很了解。我们可以将一条链表想象成环环相扣的结点,就如平常所见到的锁链一样。链表内包含很多结点(当然也可以包含零个结点)。其中每个结点的数据空间一般会包含一个数据结构(用于存放各种类型的数据)以及一个指针,该指针一般称为next,用来指向下一个结点的位置。由于下一个结点也是链表类型,所以next的指针也要定义为链表类型。例如以下语句即定义了链表的结构类型。
定义链表结构体类型:
//链表的类型
struct Linklist
{
int val;
Linklist *next;
};
链表初始化:
Linklist* Init()
{
Linklist *HeadNode = (Linklist*)malloc(sizeof(Linklist));//创建节点
if (HeadNode==NULL)
{
printf("没有内存可以开辟");
return HeadNode;
}
HeadNode->val = 0;
HeadNode->next = NULL;
return HeadNode;
}
这里引申一下,此处例程中返回的链表指针为该链表的头结点,相对应的还有一个头指针的概念。头指针内只有指针的元素,并没有数据元素,但头结点除了指针还有数据。
头指针就是链表的名字,仅仅是个指针而已。头结点是为了操作的统一与方便而设立的,放在第一个有效元素结点(首元结点)之前,其数据域一般无意义(当然有些情况下也可存放链表的长度、用做监视哨等等)。一般情况下见到的链表的指针多为头指针,但最近在一个程序员编程网站leetcode中发现,题目中所给的链表一般是首元结点作为第一个元素,而不是头指针。
链表创建
创建链表需要将既定数据按照链表的结构进行存储,本文以一种最简单的方式来演示:使用数组对链表赋值。将原来在连续空间存放的数组数据,放置在不连续的链表空间中,使用指针进行链接。
链表创建的步骤一般使用给定的头指针以及需要初始化的数据序列作为输入参数,本文使用数组作为输入数据序列。在下面的例程中,先将首元结点使用数组第一个元素初始化,再在首元结点之后创建新的链表结点赋值数组内余下的数据。具体实现如下:
void Creat_Linklist(Linklist *HeadNode,int *Indata,int Data_num)
{
Linklist *Current = (Linklist *)HeadNode;
int i = 0;
for (i = 0; i < Data_num; i++)
{
Current->val = Indata[i];
if (i<Data_num-1)//由于每次要开辟新的节点,条件限制是为了减少费节点
{
Current->next = (Linklist *)malloc(sizeof(Linklist));//开辟一块区域
Current = Current->next;
}
Current->next = NULL;
}
}
显示链表
//显示链表
void Show_Linklist(Linklist *HeadNode)
{
Linklist *Current = HeadNode;
while (Current)
{
printf("元素为%d", Current->val);
Current = Current->next;
}
}
插入链表节点
链表创建完之后,下面我们将介绍如何向链表内插入结点。一般添加结点可以分为两类:一类是在链表尾部插入;另一类为在中间插入。
链表结尾添加结点的步骤就是新建一个链表结点,将其链接到当前链表尾指针。
在中间结点插入结点的步骤稍微复杂一些,其中也包含两种情况,分别是在指定结点前插入和指定结点后插入。其操作原理一样,本文只对指定位置后插入结点进行介绍。指定结点前插入结点留给大家尝试。
假设一个链表内存在几个几点A1,A2,A3,A4….,当根据要求需要在指定位置之后(比如A2结点)插入一个新结点时。首先我们需要新建立一个结点NodeToInsert,然后将新结点的next指向A3,并且将A2的next指针指向新建立的结点NodeToInsert,切记操作顺序不要改变。如果操作顺序变换一下,先将A2的next指向了新建立的结点,那么我们就丢失了A3的寻址方式。因此,在将A2的next指向其他任何地方之前,请务必将A3的地址存在NodeToInsert或者某个新建节点内。
//插入链表节点
bool Insert_List(Linklist *HeadNode, int Indata_dex, int data)
{
Linklist *Current = (Linklist *)HeadNode;
int i = 1;
while (Current&&i < Indata_dex-1)
{
Current = Current->next;
i+=1;
}
Linklist *Insert = (Linklist *)malloc(sizeof(Linklist));//创建节点
if (Insert == NULL)
{
printf("内存不足");
return false;
}
Insert->val = data;
Insert->next = Current->next;//将原本位置的地址,放在插入元素的后面
Current->next = Insert;//
return true;
}
删除链表节点
对应于插入链表结点,链表的基本操作中同样也有删除链表结点。删除结点包括删除指定位置的结点和指定元素的结点。其基本原理都是先锁定待删除的结点的位置,然后将该结点的后置结点链接到前置结点的next指针处。这样中间这个结点即我们要删除的结点就从原来的链表中脱离开来。相对于原来的链表,即删除了该结点。
//删除链表节点
bool Delect_Link_list(Linklist * HeadNode, int index, int *DataToDel)
{
Linklist*Current = (Linklist*)HeadNode;
int i = 1;
while (Current&&i < index-1)
{
Current = Current->next;
i++;
}
Linklist* Delete = (Linklist*)malloc(sizeof(Linklist));
Delete = Current->next;
*DataToDel = Current->next->val;
printf("Delect is %d", *DataToDel);
Current->next = Current->next->next;
free(Delete);
return true;
}
获取链表的长度
int Length_List(Linklist* HeadNode)
{
Linklist *Current = (Linklist*)HeadNode;
int i = 0;
while (Current)
{
i++;
Current = Current->next;
}
return i;
}
获取链表的元素位置
//获取链表的元素
int Locate_val(Linklist* HeadNode, int Data_locate)
{
Linklist*Current = (Linklist*)HeadNode;
int i = 1;
while (Current)
{
if (Current->val == Data_locate)
{
return i;
}
Current = Current->next;
i++;
}
return -1;//没有该值
}
销毁链表
//链表置空,销毁链表
bool Destory_List(Linklist*HeadNode)
{
Linklist*Current = (Linklist*)HeadNode;
Linklist *pNext;
while (Current)
{
pNext = Current->next;
free(Current);
Current = pNext;
}
HeadNode->next = NULL;
return true;
}
判断链表是否有环
//链表判断是否有环
bool isListLoop(Linklist*HeadNode)
{
Linklist*slow, *fast;
fast = slow = HeadNode;
while (fast&&slow)
{
slow = slow->next;
if (fast->next)
{
fast = fast->next->next;
}
else
{
fast = fast->next;
}
if (slow==fast)
{
return true;
}
}
return false;
}
参考论文:https://blog.csdn.net/u012531536/article/details/80170893