链表:
概念:链表是一种物理存储结构上非连续、非顺序,逻辑上是连续,有序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。链表由一个个的节点构成,每个节点至少包含两个域,一个指针域和一个值域,通过指针域内的指针找到下一个节点的位置,值域中存放有效数据元素。基于这种指针指向下一个节点的特性,将这种数据结构形象地称之为链表。
链表的分类:
单向或双向
单向链表中每个节点有两个域,一个指针域(p->next)和一个值域(p->data),指针域指向下一个节点的位置或者如果是链表的最后一个节点则指向NULL(p->next=NULL),值域中存放有效数据元素
双向链表中每个节点有三个域,两个指针域分别指向前一个节点的位置(p->prev)和后一个节点的位置(p->next)或者如果是链表的最后一个节点则指向NULL(p->next=NULL),值域中存放有效数据元素
带头或不带头
如何区分带头不带头,看链表中第一个节点中的值域是否存放的是有效数据元素,如果是有效数据,说明不带头,如果不是一个有效数据元素,是个随机值或者无意义的值,那就是头节点
对于带头结点的链表,每次在头部插入新元素时,只需使新元素的next指向链表中的第一个节点,然后使头结点的next指向新插入的元素即可,无需改变头结点指针的指向,因此,传参时传递一级指针即可
对于不带头结点的链表,在头部插入新元素时,要使新元素的next指向链表中的第一个节点,然后使指向链表的指针指向新元素所在的位置,因此,需要改变指针的指向,在传递参数时,应当传递指针的地址,参数用二级指针接收
循环或不循环
以上展示的图解都是不循环的链表
循环链表是在原有链表的基础上使最后一个节点的next不再指向NULL,而是指向链表中第一个节点的位置(带头则指向头结点)
单向或双向、带头或不带头、循环或不循环组合在一起可以有八种链表
最常用的是无头单向非循环链表
和带头双向循环链表
对于无头单向非循环链表,简称单链表
单链表的一些思想:
如何判断一个单链表是否带环?(以下默认慢指针每次走一步,快指针每次走两步)
利用快慢指针(慢指针每次走一步,快指针每次走两步),如果存在环,快指针率先进入环内,开始在环内循环,慢指针后入环,将会和快指针在环内相遇,可以得到一个相遇节点MeetNode
如果快慢指针中,慢指针每次走一步,快指针每次走的步数大于等于3,则有可能无法判定环是否存在
因为,假设快指针每次走fs步,环一圈的节点数为M(M>=2)
一旦fs-1=n*M,如果在慢指针入环时未能和快指针相遇,那么它们就永远无法相遇,它们的距离始终保持一致
单链表中如果存在环,如何得到这个环的入口的位置?
- 首先判断是否带环,若带环,可以得到一个快慢指针相遇节点的位置
接下来有几种方法可以使用
1、从链表的头结点开始,往后依次取出每一个节点,将环内节点遍历,同环内节点一一比较,若节点与环内节点相等,说明该节点即为环上的一个节点,也就是环的入口处的节点
2、定义两个指针,一个从链表的头结点开始,一个从相遇节点开始,每次同时往后走一步,当这两个指针相遇时,相遇位置即为环的入口节点。
对于快指针:路程fast=L+X+nR
对于慢指针:路程slow=L+X
快指针=2*慢指针------>L+X+nR=2*(L+X)------->L=nR-X
所以当一个指针从头出发,一个指针从相遇节点出发,他们会相遇在入口节点
当慢指针进环后, 在慢指针走完一圈之前,快指针是一定可以追上慢指针的
最差情况:慢指针进环时,快指针刚好在慢指针的下一个节点的位置,当慢指针走过半圈,快指针则是一圈,回到入口节点的下一个节点的位置上,慢指针还剩半圈,当慢指针再走半圈,快指针又是一圈,必然先于慢指针经过入口节点。
假设环内一共20个节点,起始位置为1,同时也是21,慢指针入环时在1,快指针在2,
慢指针走到11,走了十步,快指针则是20步,快指针在22=20+2,当慢指针走到20,走了9步,
快指针走了18步,22+18=40=20+20,追上了慢指针
也可以这么理解,每走一步,快慢指针之间的距离就会缩小1,最坏情况,它们之间差19步,当慢指针走过19步,到第20个节点时,快指针也正好将它追上了。
链表相交问题:
两个链表是否相交,若相交找出交点
首先判断两个链表的类型:
链表A 链表B
0 0
0 1
1 0
1 1
1表示带环,0表示不带环
利用快慢指针判断出是否带环
两个链表都不带环的情况下
1、先判断是否相交:
遍历两个链表到最后一个节点,如果当中某个节点相等,说明相交,否则不相交
2、求交点
第一步遍历链表得到两个链表的长度,让较长的链表指针先走长度差的步数,之后两个链表指针同时向前移动,比较两个链表指针指向的节点,如果节点相等,则就是交点
两个链表如果相交,不存在一个带环一个不带环的情况
两个链表都带环
1、先判断是否相交
无法采用之前判断不带环单链表是否相交时的遍历方法,因为带环链表无法遍历,会陷入死循环,可以利用判环过程得到的两个相遇点,MeetNode1和MeetNode2,如果两个带环链表相交,那么这两个相遇点都在环上,可以对这两个相遇点设置两个指针,一个快指针,一个慢指针,如果相遇点
相遇,说明两个相遇点在一个环上,也就说明两个链表相交。
2、求交点
对于两个链表都带环的情况,交点有可能是在环上(环内相交),即入口点,也有可能是在环外(环外相交)
环外相交:取一相遇点作为断点,这样带环链表可看做不带环链表,遍历求得两个链表的长度,让较长链表的链表指针先走长度差值,之后两个链表指针共同前进,指针相等处即为交点
环内相交:分别求得各自的入口点即为交点
附上单链表和双向循环链表以及一些相关操作的源码:
单链表的头文件:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int DataType;
typedef struct SListNode
{
struct SListNode* next;
DataType data;
}Node;
Node* BuySListNode(DataType data);
void SListBackPush(Node** pplist,DataType data);//单链表的尾插
void SListBackPop(Node** pplist);//单链表的尾删
void SListPrint(Node* pplist);//将链表打印
void SListFrontPush(Node** pplist, DataType data);//单链表的头插
void SListFrontPop(Node** pplist);//单链表的头删
Node* SListFind(Node* pplist, DataType data);//单链表的查找
void SListInsertAfter(Node* pos, DataType data);//在单链表pos位置插入data
void SListEraseAfter(Node* pos);//删除单链表pos位置后数据
int SListSize(Node* pplist);//返回单链表的长度
void SListDestroy(Node** pplist);//单链表的销毁
单链表的源.c文件:
#include"Slist.h"
Node* BuySListNode(DataType data)//申请一个链表节点,并初始化
{
Node* newNode = (Node*)malloc(sizeof(Node));
assert(newNode);
newNode->data = data;
newNode->next = NULL;
return newNode;
}
void SListPrint(Node*pplist)//将链表的值打印
{
Node *cur= pplist;
while (cur!= NULL)
{
printf("%d---->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
void SListBackPush(Node** pplist, DataType data)//链表尾插
{
Node* newNode = BuySListNode(data);//创建一个新的节点
//1、链表为空
if (*pplist == NULL)
{
*pplist = newNode;
}
//2、链表非空
else
{
Node* cur = *pplist;
while (cur->next)//遍历链表,找到最后一个节点
{
cur = cur->next;
}
cur->next = newNode;//尾插
}
}
void SListBackPop(Node** pplist)//链表的尾删
{
assert(pplist);
if (*pplist == NULL)//如果链表为空,返回
{
assert(0);
return;
}
else if (NULL == (*pplist)->next)//如果链表中只有一个节点
{
free(*pplist); //删除最后一个节点
}
else
{
Node* cur = *pplist;
Node* prev = NULL;
while (cur->next != NULL)
{
prev = cur;
cur = cur->next;
}
free(cur);
prev->next = NULL;
}
}
void SListFrontPush(Node** pplist, DataType data)//单链表的头插
{
/*Node*newNode=BuySlistNode(data);
if (*pplist == NULL)
{
*pplist = newNode;
}
else
{
newNode->next = *pplist;
*pplist = newNode;
}*/
assert(pplist);
Node* newNode = BuySListNode(data);
newNode->next = *pplist;//这两步的顺序不能颠倒,先让插入的节点和链表连接,再更新链表的第一个节点的位置
//如果先更新链表第一个节点的位置为新创建的节点,那么原有链表的数据就会丢失
*pplist = newNode;
}
void SListFrontPop(Node** pplist)//单链表的头删
{
/*assert(pplist);
if (*pplist == NULL)
return;
else if (NULL == (*pplist)->next)
{
free(*pplist);
}
else
{
Node* delNode = *pplist;
*pplist = delNode->next;
free(delNode);
}*/
assert(pplist);
if (*pplist == NULL)
{
return;
}
Node* delNode = *pplist;
*pplist = delNode->next;
free(delNode);
}
Node* SListFind(Node* pplist, DataType data)//单链表的查找,找到返回该节点,没找到则返回一个NULL
{
assert(pplist);
Node* cur = pplist;
while (cur)
{
if (cur->data == data)
{
printf("数据元素%d在链表中\n", data);
return cur;
}
cur = cur->next;
}
return NULL;
}
void SListInsertAfter(Node* pos , DataType data)//单链表在pos位置之后插入data
{
if (pos == NULL)
{
return;
}
Node* newNode = BuySListNode(data);
newNode->next = pos->next;
pos->next = newNode;
}
void SListEraseAfter(Node* pos)//删除单链表pos位置后的节点
{
if (pos == NULL||pos->next==NULL)
{
return;
}
Node* delNode = pos->next;
pos->next = delNode->next;
free(delNode);
}
int SListSize(Node* pplist)//返回单链表的长度
{
assert(pplist);
Node* cur = pplist;
int count = 0;
while (cur)
{
cur = cur->next;
count++;
}
return count;
}
void SListDestroy(Node** pplist)//单链表的销毁
{
assert(pplist);
Node* cur = *pplist;
while (cur)
{
*pplist = cur->next;
free(cur);
cur = *pplist;
}
}
void TestSList()
{
Node* pplist = NULL;
SListBackPush(&pplist, 1);//尾插5个数据
SListBackPush(&pplist, 2);
SListBackPush(&pplist, 3);
SListBackPush(&pplist, 4);
SListBackPush(&pplist, 5);
SListPrint(pplist);
SListFrontPush(&pplist, 5);//头插5个数据
SListFrontPush(&pplist, 4);
SListFrontPush(&pplist, 3);
SListFrontPush(&pplist, 2);
SListFrontPush(&pplist, 1);
SListPrint(pplist);
SListBackPop(&pplist);//尾删5个数据
SListBackPop(&pplist);
SListBackPop(&pplist);
SListBackPop(&pplist);
SListBackPop(&pplist);
SListInsertAfter(SListFind(pplist,3), 999);//在数据元素为3的节点后插入999
int size=SListSize(pplist);//返回链表的长度
printf("%d\n", size);
SListPrint(pplist);
SListEraseAfter(SListFind(pplist, 999));//删除在数据元素为999的节点的后面的节点
SListPrint(pplist);
Node* ret = SListFind(pplist, 5);//查找链表中是否有5,若有则返回该节点
assert(ret);
printf("%d\n", ret->data);
SListFrontPop(&pplist);
SListFrontPop(&pplist);
SListFrontPop(&pplist);//头删5个数据
SListFrontPop(&pplist);
SListPrint(pplist);
SListFrontPop(&pplist);
SListPrint(pplist);
}
双向循环链表的头文件:
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
typedef int DataType;
typedef struct DListNode//声明并定义一个双向循环链表节点的结构体
{
struct DListNode* next;
struct DListNode* prev;
DataType data;
}DListNode;
DListNode* BuyNode(DataType data);//初始化创建一个链表节点
DListNode* DListCreat();//创建一个链表的头结点
bool IsDListEmpty(DListNode* list);//判断链表是否为空
void DListPrint(DListNode* list);//打印双向循环列表
void DListBackPush(DListNode*list,DataType data);//双向循环链表的尾插
void DListBackPop(DListNode* list);//双向循环链表的尾删
void DListFrontPush(DListNode* list, DataType data);//双向循环链表的头插
void DListFrontPop(DListNode* list);//双向循环链表的头删
DListNode* DListFind(DListNode* list,DataType data);//查找双向循环链表中是否有数据元素为data的节点,若有,返回该节点的位置
void DListPosPush(DListNode* pos, DataType data);//在双向循环链表中的pos位置之前插入一个值为data的节点
void DListPosPop(DListNode* pos);//删除双向循环链表中pos位置的节点
void DListDestroy(DListNode* list);//双向循环链表的销毁
双向循环链表的源.c文件:
#include"DList.h"
DListNode* BuyNode(DataType data)//创建一个新的节点
{
DListNode* newNode = (DListNode*)malloc(sizeof(DListNode));
assert(newNode);
newNode->next = NULL;
newNode->prev = NULL;
newNode->data = data;
return newNode;
}
DListNode* DListCreat()//创建一个链表的头结点,初始时,双向循环链表的头结点都指向头结点自己
{
DListNode* head = BuyNode(0);
head->next = head;
head->prev = head;
return head;
}
bool IsDListEmpty(DListNode* list)//判断链表是否为空,为空返回true
{
assert(list);
if (list->prev == list)
return true;
else return false;
}
void DListBackPush(DListNode*list,DataType data)//双向循环链表的尾插
{
DListNode*Node=BuyNode(data);//1、先将新节点连上 2、改变上一个节点和头结点的指向
Node->prev = list->prev;//新节点的prev和链表最后一个节点连上(新节点prev解决)
Node->next = list;//新节点的next指向头结点(新节点的next解决),这两步顺序可以颠倒
list->prev->next = Node;//最后一个节点next指向新节点,(最后一个节点的next重置)
list->prev = Node;//头结点指向新插入的节点,头结点prev重置
}
void DListBackPop(DListNode* list)//双向循环链表的尾删
{
if (IsDListEmpty(list))//为空直接返回
{
return;
}
DListNode* delNode = list->prev;
delNode->prev->next = list;
list->prev = delNode->prev;
free(delNode);
}
void DListFrontPush(DListNode* list, DataType data)//双向循环链表的头插
{
assert(list);
DListNode* Node = BuyNode(data);
Node->next = list->next;
Node->next->prev = Node;
list->next = Node;
Node->prev = list;
}
void DListFrontPop(DListNode* list)//双向循环链表的头删
{
if (IsDListEmpty(list))
{
return;
}
DListNode* delNode = list->next;
list->next = delNode->next;
delNode->next->prev = list;
free(delNode);
}
void DListPrint(DListNode* list)//打印链表中所有结点的值
{
if (IsDListEmpty(list))
{
printf("链表为空,无法打印");
return;
}
DListNode* cur = list;
while (cur->next != list)
{
printf("%d\t", cur->next->data);
cur = cur->next;
}
printf("\n");
}
DListNode* DListFind(DListNode* list, DataType data)//查找双向循环链表中是否有数据元素为data的节点,若有,返回该节点的位置
{
if (IsDListEmpty(list))
{
return;
}
int count = 1;
DListNode* cur = list->next;
while (cur!= list)
{
if (cur->data == data)
{
printf("该节点在链表中是%d号节点\n", count);
return cur;
}
cur = cur->next;
count++;
}
}
void DListPosPush(DListNode* pos, DataType data)//在双向循环链表中的pos位置之前插入一个值为data的节点
{
assert(pos);
DListNode* newNode = BuyNode(data);
pos->prev->next = newNode;
newNode->next = pos;
newNode->prev = pos->prev;
pos->prev = newNode;
}
void DListPosPop(DListNode* pos)//删除双向循环链表中pos位置的节点
{
if (pos == NULL)
{
return;
}
pos->prev->next = pos->next;
pos->next->prev = pos -> prev;
free(pos);
}
void DListDestroy(DListNode** list)//双向循环链表的销毁
{
assert(list);
DListNode* cur = (*list)->next;
while (cur!=(*list))
{
(*list)->next = cur->next;
free(cur);
cur =(*list)->next;
}
free(*list);
(*list) = NULL;
}
void TestDList()
{
DListNode* list = DListCreat();//创建链表的头结点
DListFrontPush(list, 1);//头插五个
DListFrontPush(list, 2);
DListFrontPush(list, 3);
DListFrontPush(list, 4);
DListFrontPush(list, 5);
DListPrint(list);//打印链表
DListBackPush(list, 1);//尾插五个
DListBackPush(list, 2);
DListBackPush(list, 3);
DListBackPush(list, 4);
DListBackPush(list, 5);
DListNode*ret=DListFind(list, 1);//找链表中值为1的节点,返回该节点
DListPosPush(ret, 6);//在该节点位置之前插入一个值为6的节点
DListPrint(list);//打印链表
DListPosPop(ret);//将查找到的节点删除
DListPrint(list);//打印链表
DListFrontPop(list);
DListFrontPop(list);
DListFrontPop(list);
DListFrontPop(list);
DListFrontPop(list);//头删五个
DListPrint(list);//打印链表
DListBackPop(list);//尾删五个
DListBackPop(list);
DListBackPop(list);
DListBackPop(list);
DListBackPop(list);
DListPrint(list);//打印链表
DListDestroy(&list);//销毁链表
}