数据结构——单向链表
链表
1.空间可以不连续(理论上长度是无限的)
2.访问元素不方便
3.链表需要更大的空间存放数据和节点地址
4.链表的插入和删除效率很高O(1)
单向链表
无头单向链表,节点插入在头的话,每次头结点都会变,所以有了有头链表,头结点的pNext总是指向链表的第一个节点
1.创建空链表
//创建空链表
LinkNode *CreateLinkList(void)
{
LinkNode *pTmpNode = NULL;
pTmpNode = malloc(sizeof(LinkNode));
if (NULL == pTmpNode)
{
return NULL;
}
pTmpNode->Data = 0;
pTmpNode->pNext = NULL;
return pTmpNode;
}
2.头插法
int HeadInsertLinkList(LinkNode *pHead, DataType TmpData)
{
LinkNode *pTmpNode = NULL;
//1.申请空间
pTmpNode = malloc(sizeof(LinkNode));
if (NULL == pTmpNode)
{
return -1;
}
//2.将Data成员初始化
pTmpNode->Data = TmpData;
//3.将pNext成员初始化(和后面连起来)
pTmpNode->pNext = pHead->pNext;
//4.将头结点的pNext指向新申请节点(和前面连起来)
pHead->pNext = pTmpNode;
return 0;
}
3.遍历
int ShowLinkList(LinkNode *pHead)
{
LinkNode *pTmpNode = NULL;
pTmpNode = pHead->pNext;
while (pTmpNode != NULL)
{
printf("%d ", pTmpNode->Data);
pTmpNode = pTmpNode->pNext;
}
printf("\n");
return 0;
}
4.修改
int UpdateLinkList(LinkNode *pHead, DataType OldData, DataType NewData)
{
LinkNode *pTmpNode = NULL;
int cnt = 0;
pTmpNode = pHead->pNext;
while (pTmpNode != NULL)
{
if (pTmpNode->Data == OldData)
{
pTmpNode->Data = NewData;
cnt++;
}
pTmpNode = pTmpNode->pNext;
}
return cnt;
}
5.查找
LinkNode *FindLinkList(LinkNode *pHead, DataType TmpData)
{
LinkNode *pTmpNode = NULL;
pTmpNode = pHead->pNext;
while (pTmpNode != NULL)
{
if (pTmpNode->Data == TmpData)
{
return pTmpNode;
}
pTmpNode = pTmpNode->pNext;
}
return NULL;
}
6.尾插
int TailInsertLinkList(LinkNode *pHead, DataType TmpData)
{
LinkNode *pNewNode = NULL;
LinkNode *pTmpNode = NULL;
pNewNode = malloc(sizeof(LinkNode));
if (NULL == pNewNode)
{
return -1;
}
pNewNode->Data = TmpData;
pNewNode->pNext = NULL;
pTmpNode = pHead;
while (pTmpNode->pNext != NULL)
{
pTmpNode = pTmpNode->pNext;
}
pTmpNode->pNext = pNewNode;
return 0;
}
7.删除链表节点
删除链表节点,必须要知道要删节点的前一个结点,所以要定义两个指针来操作
int DeleteLinkList(LinkNode *pHead, DataType TmpData)
{
LinkNode *pPreNode = NULL;
LinkNode *pTmpNode = NULL;
int cnt = 0;
pPreNode = pHead;
pTmpNode = pHead->pNext;
while (pTmpNode != NULL)
{
if (pTmpNode->Data == TmpData)
{
//删除
pPreNode->pNext = pTmpNode->pNext;
free(pTmpNode);
pTmpNode = pPreNode->pNext;
cnt++;
}
else
{
pTmpNode = pTmpNode->pNext;
pPreNode = pPreNode->pNext;
}
}
return cnt;
}
注意:free掉pTmpNode后,它变成了野指针,要为它重新赋值
8.销毁
int DestroyLinkList(LinkNode **ppHead)
{
LinkNode *pTmpNode = NULL;
LinkNode *pFreeNode = NULL;
pTmpNode = pFreeNode = *ppHead;
while (pTmpNode != NULL)
{
pTmpNode = pTmpNode->pNext;
free(pFreeNode);
pFreeNode = pTmpNode;
}
*ppHead = NULL;
return 0;
}
复杂操作
1.查找链表中间节点
算法:快指针pFast每次走2步,慢指针pSlow每次走1步,快指针到达末尾时,慢指针所在的位置即为中间位置
LinkNode *FindMidLinkNode(LinkNode *pHead)
{
LinkNode *pFast = NULL;
LinkNode *pSlow = NULL;
pFast = pSlow = pHead->pNext;
while (pFast != NULL)
{
pFast = pFast->pNext;
if (NULL == pFast)
{
break;
}
pFast = pFast->pNext;
if (NULL == pFast)
{
break;
}
pSlow = pSlow->pNext;
}
return pSlow;
}
2.查找链表倒数第k个节点
算法:快指针先走k步,慢指针和快指针每次走1步(快指针总是领先慢指针k步),当快指针走到末尾时,慢指针即指向链表倒数第k个节点
LinkNode *FindLastKthLinkNode(LinkNode *pHead, int k)
{
LinkNode *pFast = pHead->pNext;
LinkNode *pSlow = pHead->pNext;
int i = 0;
for (i = 0; i < k; i++)
{
pFast = pFast->pNext;
if (NULL == pFast)
{
break;
}
}
if (NULL == pFast)
{
return NULL;
}
while (pFast != NULL)
{
pSlow = pSlow->pNext;
pFast = pFast->pNext;
}
return pSlow;
}
3.链表的倒置(反转)
算法:例如:将第头节点与第一个节点断开连接,在断开前都要保存一下头节点的下一个节点,然后向头插法一样将第一个节点插入到链表中,依次类推
int ReversalLinkList(LinkNode *pHead)
{
LinkNode *pTmpNode = NULL;
LinkNode *pInsertNode = NULL;
pTmpNode = pHead->pNext;
pHead->pNext = NULL;
pInsertNode = pTmpNode;
while (pTmpNode != NULL)
{
pTmpNode = pTmpNode->pNext;
pInsertNode->pNext = pHead->pNext;
pHead->pNext = pInsertNode;
pInsertNode = pTmpNode;
}
return 0;
}
4.链表的排序(冒泡排序、选择排序)
冒泡排序
每轮比较之后pTmpNode1,都是下一轮的pEnd,直到pEnd = pHead->pNext->pNext;
//链表排序(冒泡排序法)
int BubbleSortLinkList(LinkNode *pHead)
{
LinkNode *pTmpNode1 = NULL;
LinkNode *pTmpNode2 = NULL;
LinkNode *pEnd = NULL;
DataType TmpData;
//如果链表没有节点或者只有1个节点返回0
if (NULL == pHead->pNext || NULL == pHead->pNext->pNext)
{
return 0;
}
while (1)
{
pTmpNode1 = pHead->pNext;
pTmpNode2 = pHead->pNext->pNext;
if (pTmpNode2 == pEnd)
{
break;
}
while (pTmpNode2 != pEnd)
{
if (pTmpNode1->Data > pTmpNode2->Data)
{
TmpData = pTmpNode1->Data;
pTmpNode1->Data = pTmpNode2->Data;
pTmpNode2->Data = TmpData;
}
pTmpNode1 = pTmpNode1->pNext;
pTmpNode2 = pTmpNode2->pNext;
}
pEnd = pTmpNode1;
}
return 0;
}
选择排序:
每次选出最小的,第二小的...最大的,
//选择排序
int SelectSortLinkList(LinkNode *pHead)
{
LinkNode *pTmpNode = NULL;
LinkNode *pMinNode = NULL;
LinkNode *pSwapNode = NULL;
DataType TmpData;
if (NULL == pHead->pNext)
{
return 0;
}
pSwapNode = pHead->pNext;
while (pSwapNode->pNext != NULL)
{
pMinNode = pSwapNode;
pTmpNode = pSwapNode->pNext;
while (pTmpNode != NULL)
{
if (pTmpNode->Data < pMinNode->Data)
{
pMinNode = pTmpNode;
}
pTmpNode = pTmpNode->pNext;
}
if (pMinNode != pSwapNode)
{
TmpData = pMinNode->Data;
pMinNode->Data = pSwapNode->Data;
pSwapNode->Data = TmpData;
}
pSwapNode = pSwapNode->pNext;
}
return 0;
}
5.已知链表中间某个节点地址,不知道头结点地址,如何删除该节点
算法:可以上中间这个的值变成下一个节点的值,并把它的pNext指向它的pNext->pNext,然后free掉空间
6.如何判断一个链表是否有环?环长?环的入口位置?
是否有环:快指针每次走2步,慢指针每次走1步,快慢指针相遇则说明有环
因为快指针第一次比慢指针多1,第二次快指针比慢指针快2,依次类推,环长一定是一个自然数,所以一定能相遇
如何计算环长:标记相遇的位置,让指针继续向后走,每走一步计算器自加,走回到标记位置,则计算器值即为环长
如何计算环入口位置:将一个指针从第一个节点向后走,将一个指针从相遇点向后走,两个指针相遇的位置即为环入口的位置
//判断链表是否有环
//实现方式:
// 快指针每次走2步,慢指针每次走1步
// 两者能够相遇即为有环
LinkNode *IsHasCircle(LinkNode *pHead, int *pcnt)
{
LinkNode *pFast = NULL;
LinkNode *pSlow = NULL;
LinkNode *pTmpNode = NULL;
LinkNode *pNode1 = NULL;
LinkNode *pNode2 = NULL;
int ret = 0;
int cnt = 1;
pSlow = pFast = pHead->pNext;
while (1)
{
pFast = pFast->pNext;
if (NULL == pFast)
{
ret = 0;
break;
}
pFast = pFast->pNext;
if (NULL == pFast)
{
ret = 0;
break;
}
pSlow = pSlow->pNext;
if (pSlow == pFast)
{
ret = 1;
break;
}
}
if (1 == ret)
{
//获得环长
pTmpNode = pSlow->pNext;
while (pTmpNode != pSlow)
{
cnt++;
pTmpNode = pTmpNode->pNext;
}
*pcnt = cnt;
//获得环入口位置
pNode1 = pSlow;
pNode2 = pHead->pNext;
while (1)
{
pNode1 = pNode1->pNext;
pNode2 = pNode2->pNext;
if (pNode1 == pNode2)
{
return pNode1;
}
}
}
return NULL;
}