链表
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;
}