单链表要实现增删改查,反转,置空等功能
分为三部分test.c ,SList.c, SList.h
以下为源代码:
*test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include "Slist.h"
#include <stdlib.h>
void menu()
{
printf("** 1.尾部插入 2.尾部删除 **\n");
printf("** 3.头部插入 4.头部删除 **\n");
printf("** 5.任意位置插入 6.任意位置删除 **\n");
printf("** 7.统计节点个数 8.返回第一个值 **\n");
printf("** 9.返回最后一个值 10.显示 **\n");
printf("** 11.释放 12.置空 **\n");
printf("** 13.逆置 14.删除指定的所有值 **\n");
printf("** 15.删除指定的所有值第二种方法 **\n");
}
void test()
{
SList pl;
SListInit(&pl);
int input = 0;
SDataType data;
SDataType val;
int count = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入要插入的数:");
scanf("%d", &data);
SListPushBack(&pl, data);
break;
case 2:
SListPopBack(&pl);
break;
case 3:
printf("请输入要插入的数:");
scanf("%d", &data);
SListPushFront(&pl, data);
break;
case 4:
SListPopFront(&pl);
break;
case 5:
printf("请输入要插入哪个数后面:");
scanf("%d", &val);
printf("请输入插入的数:");
scanf("%d", &data);
SListInsertAfter(SListFind(&pl, val), data);
break;
case 6:
printf("请输入要删除的数:");
scanf("%d", &val);
SListErase(&pl, SListFind(&pl, val));
break;
case 7:
count = SListSize(&pl);
printf("节点个数为:%d\n", count);
break;
case 8:
data=SListFront(&pl);
printf("第一个值为:%d\n", data);
break;
case 9:
data=SListBack(&pl);
printf("最后一个值为:%d\n", data);
break;
case 10:
PrintSList(&pl);
break;
case 11:
SListDestroy(&pl);
break;
case 12:
SListEmpty(&pl);
printf("置空成功!\n");
break;
case 13:
ReverseSList(&pl);
break;
case 14:
printf("请输入要删除的值:");
scanf("%d", &data);
removeElements1(&pl, data);//删除所有为data的值
break;
case 15:
printf("请输入要删除的值:");
scanf("%d", &data);
removeElements2(&pl, data);//删除所有为data的值
break;
}
} while (input);
}
int main()
{
test();
system("pause");
return 0;
}
- SList.h
#pragma once
typedef int SDataType;
typedef struct SListNode
{
SDataType _data;
struct SListNode* _pNext;
}Node;
typedef struct SList
{
Node* pHead;
}SList;
void SListInit(SList *pl);
void SListPushBack(SList *pl, SDataType data);//尾插
void SListPopBack(SList *pl);//尾删
void SListPushFront(SList *pl, SDataType data);//头插
void SListPopFront(SList *pl);//头删
void SListInsertAfter(Node *pos, SDataType data);//任意位置插入
void SListErase(SList *pl, Node *pos);//任意位置删除
Node *SListFind(SList *pl, SDataType data);//查找
int SListSize(SList *pl);//找寻节点个数
SDataType SListFront(SList *pl);//获取第一个节点
SDataType SListBack(SList *pl);//必须保证有尾结点,获取最后一个节点
void PrintSList(SList *pl);
void SListDestroy(SList *pl);
int SListEmpty(SList *pl);
Node* ReverseSList(SList *pl);
Node* removeElements1(SList *pl, SDataType val);//删除所有为val的值
Node *removeElements2(SList *pl, SDataType val);//删除所有为val的结点第二种方法
- SList.c
#include "SList.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
void SListInit(SList *pl)
{
assert(pl);
pl->pHead = NULL;
}
Node* BuySListNode(SDataType data)//构造结点
{
Node* pNewNode = (Node*)malloc(sizeof(Node));
if (pNewNode == NULL)
{
assert(0);
return NULL;
}
pNewNode->_data = data;
pNewNode->_pNext = NULL;
return pNewNode;
}
void SListPushBack(SList *pl, SDataType data)//尾插
{
Node *PNewNode = NULL;
assert(pl);
Node *Pcur = NULL;
PNewNode = BuySListNode(data);
Pcur = pl->pHead;//从头开始
if (pl->pHead == NULL)//如果头结点为空,直接放入新结点
{
pl->pHead = PNewNode;
}
else
{
while (Pcur->_pNext)//只要一直存在下一个节点,则继续遍历
{
Pcur = Pcur->_pNext;
}
Pcur->_pNext = PNewNode;
}
}
void SListPopBack(SList *pl)//尾删
{
assert(pl);
if (pl->pHead == NULL)
{
assert(0);
return;
}
else if (pl->pHead->_pNext == NULL)
{
free(pl->pHead);
pl->pHead = NULL;
}
else
{
Node *pPreNode = NULL;//前一个节点
Node *pTailNode = pl->pHead;//尾结点
while (pTailNode->_pNext)
{
pPreNode = pTailNode;
pTailNode = pTailNode->_pNext;
}
free(pTailNode);
pPreNode->_pNext = NULL;
}
}
void SListPushFront(SList *pl, SDataType data)//头插
{
Node *pNewNode = NULL;
assert(pl);
pNewNode = BuySListNode(data);
pNewNode->_pNext = pl->pHead;
pl->pHead = pNewNode;
}
void SListPopFront(SList *pl)//头删
{
assert(pl);
if (pl->pHead == NULL)
return;
else
{
Node *pDelNode = pl->pHead;
pl->pHead = pDelNode->_pNext;
free(pDelNode);
}
}
Node* SListFind(SList *pl,SDataType data)
{
assert(pl);
Node *pCur = pl->pHead;
while (pCur)
{
if (pCur->_data == data)
{
return pCur;
}
else
pCur = pCur->_pNext;
}
return pCur;
}
void SListInsertAfter(Node *pos, SDataType data)//任意位置插入
{
Node *pNewNode = NULL;
if (pos == NULL)
return;
pNewNode = BuySListNode(data);
pNewNode->_pNext = pos->_pNext;
pos->_pNext = pNewNode;
}
void SListErase(SList *pl, Node *pos)//任意位置删除
{
Node* pPreNode = NULL;
assert(pl);
if (pl->pHead==NULL||pos == NULL)
return;
if (pos==pl->pHead)//只有一个结点
{
pl->pHead = pos->_pNext;
free(pos);
return;
}
pPreNode = pl->pHead;
while (pPreNode->_pNext != pos)
{
pPreNode = pPreNode->_pNext;
}
pPreNode->_pNext = pos->_pNext;
free(pos);
}
int SListSize(SList *pl)//找寻节点个数
{
Node *pCur = NULL;
int count = 0;
assert(pl);
pCur = pl->pHead;
while (pCur)
{
pCur = pCur->_pNext;
count++;
}
return count;
}
SDataType SListFront(SList *pl)//获取第一个节点
{
assert(pl);
return pl->pHead->_data;
}
SDataType SListBack(SList *pl)//必须保证有尾结点,获取最后一个节点
{
Node *pCur = NULL;
assert(pl);
pCur = pl->pHead;
while (pCur->_pNext)
{
pCur = pCur->_pNext;
}
return pCur->_data;
}
void PrintSList(SList *pl)//显示
{
Node *pCur = NULL;
assert(pl);
pCur = pl->pHead;
while (pCur)
{
printf("%d---->", pCur->_data);
pCur = pCur->_pNext;
}
printf("NULL\n");
}
void SListDestroy(SList *pl)
{
Node* pCur = NULL;
assert(pl);
pCur = pl->pHead;
while (pCur)
{
pl->pHead = pCur->_pNext;
free(pCur);
pCur = pl->pHead;
}
pl->pHead = NULL;
}
int SListEmpty(SList *pl)//置空
{
assert(pl);
return NULL == pl->pHead;
}
Node* ReverseSList(SList *pl)//逆置
{
Node* p = NULL;
Node* next = NULL;
if (pl->PHead == NULL)
return NULL;
p = pl->PHead->_PNext;
pl->PHead->_PNext = NULL;//将第一个结点后的结点置空
while (p != NULL) {
next = p->_PNext;
p->_PNext = pl->PHead;
pl->PHead = p;
p = next;
}
return pl->PHead;
}
Node* removeElements1(SList *pl, SDataType val)//删除所有为val的值
{
Node *pPreNode = NULL;
Node *pCur = NULL;
assert(pl);
pPreNode = pl->pHead;
pCur = pl->pHead->_pNext;
if (pl->pHead == NULL)
return NULL;
while (pCur)
{
if (pCur->_data == val)
{
pPreNode->_pNext = pCur->_pNext;
}
else
{
pPreNode = pCur;
}
pCur = pCur->_pNext;
}
if (pl->pHead->_data == val)
{
return pl->pHead->_pNext;
}
else
return pl->pHead;
}
Node *removeElements2(SList *pl, SDataType val)//删除所有为val的结点第二种方法
//遇到不等于val的就尾插到另一个链表中
{
Node *ResultHead = NULL;//另一链表
Node *cur = pl->pHead;
Node *last = NULL;
while (cur != NULL)
{
Node* next = cur->_pNext;
if (cur->_data != val)
{
if (ResultHead == NULL)//如果结果链表为空,直接插入
{
ResultHead = cur;
}
else//尾插
{
cur->_pNext = NULL;
last->_pNext = cur;
}
last = cur;
}
cur = next;
}
return ResultHead;
}
分析:反转单链表:遍历链表,从第二个结点起,摘下每个结点,头插到原始链表中,将第一个节点后的结点销毁
删除所有值为val的值:有两种方法,第一种是在从第二个结点开始遍历,如果遇到要删除的值时,删除,让前一个结点指针指向它的下一个,如果没有遇到,前一个结点指针指向它,遍历下一个,最后判断第一个节点是否是要删除的值,如果是返回第二个节点,否则返回头一个节点;第二种方法是定义一个结果链表,用于保存值不等于val的结点,即遍历当遇到不等于val的就尾插到结果链表中。
- 还有两种特殊情况:
1.不带头结点,也不用链表结构时应该用二级指针,例如实现尾插
void SListPushBack2(Node**PHead, SDataType data);//不用链表结构的尾插,不带头结点
void SListPushBack2(Node**PHead, SDataType data)//不用链表结构的尾插,不带头结点
{
assert(PHead);
Node *PNewNode = BuySListNode(data);
if (NULL == *PHead)
{
*PHead = PNewNode;
}
else
{
Node *cur = NULL;
cur=*PHead;
while (cur->_PNext)
{
cur = cur->_PNext;
}
cur->_PNext = PNewNode;
}
}
分析:因为在实现过程中,链表指针发生了改变,必须传链表地址,因此应该用二级指针来接收链表
2.带头节点的单链表,例如实现头插
void SListPushFront2(Node *NodeHead, SDataType data)//带头节点的头插
{
assert(NodeHead);
Node *PNewNode = BuySListNode(data);
PNewNode->_PNext = NodeHead->_PNext;
NodeHead->_PNext = PNewNode;
}
头结点为没有有效元素的结点,只要链表不为空,头结点一直存在
单链表的合并
源代码如下:
typedef struct ListNode
{
int data;
struct ListNode *next;
}Node;
Node* mergeTwoList(Node *s1, Node *s2)
{
Node *ResultHead = NULL;
Node *last = NULL;
Node *c1 = s1;
Node *c2 = s2;
if (s1 == NULL)
return s2;
if (s2 == NULL)
return s1;
while (c1 != NULL&&c2 != NULL)
{
if (c1->data <= c2->data)
{
Node *next = c1->next;
//将c1尾插到ResultHead
c1->next = NULL;
if (ResultHead == NULL)
{
ResultHead = c1;
}
else
{
last->next = c1;
}
last = c1;
c1 = next;
}
else
{
Node *next = c2->next;
//将c1尾插到ResultHead
c2->next = NULL;
if (ResultHead == NULL)
{
ResultHead = c2;
}
else
{
last->next = c2;
}
last = c2;
c2 = next;
}
}
if (c1 != NULL)
last->next = c1;
if (c2 != NULL)
last->next=c2;
return ResultHead;
}
Node* createList1()
{
Node *n1 = (Node*)malloc(sizeof(Node));
n1->data = 1;
Node *n2 = (Node*)malloc(sizeof(Node));
n2->data = 2;
Node *n3 = (Node*)malloc(sizeof(Node));
n3->data = 3;
Node *n4 = (Node*)malloc(sizeof(Node));
n4->data = 4;
Node *n5 = (Node*)malloc(sizeof(Node));
n5->data = 5;
n1->next = n2;
n2->next = n3;
n3->next = n4;
n4->next = n5;
n5->next = NULL;
return n1;
}
Node* createList2()
{
Node *n1 = (Node*)malloc(sizeof(Node));
n1->data = 1;
Node *n2 = (Node*)malloc(sizeof(Node));
n2->data = 3;
Node *n3 = (Node*)malloc(sizeof(Node));
n3->data = 5;
Node *n4 = (Node*)malloc(sizeof(Node));
n4->data = 7;
n1->next = n2;
n2->next = n3;
n3->next = n4;
n4->next = NULL;
return n1;
}
int main()
{
Node* cur = NULL;
Node* PH1=createList1();
Node* PH2=createList2();
Node* Rhead=mergeTwoList(PH1, PH2);
for (cur = Rhead; cur != NULL; cur = cur->next)
{
printf("%d ", cur->data);
}
printf("\n");
system("pause");
return 0;
}
分析:实现将两个有序链表,将两个链表合并后还是有序,定义一个结果链表,遍历两个链表,将值较小的先尾插到结果链表中,最后再判断哪个链表中还有没有剩余结点,若有,尾插到结果链表,返回结果链表。
源代码见:(github)
https://github.com/wangbiy/DS/tree/master/test_2019_6_28_1
附加:用C++实现源代码(学习C++与Linux后)(github)
https://github.com/wangbiy/review/tree/master/SList
链表例题
- 编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 。
//编写代码,以给定值x为基准将链表分割成两部分,
//所有小于x的结点排在大于或等于x的结点之前 。
Node* partition(Node* head, int x)
{
Node* small = NULL;
Node* big = NULL;
Node* smallLast = NULL;
Node* bigLast = NULL;
Node* cur = head;
while (cur!=NULL)
{
Node* next = cur->next;
if (cur->val < x)
//尾插到small链表中
{
cur->next = NULL;
if (small == NULL)
{
small = cur;
}
else
{
smallLast->next = cur;
}
smallLast = cur;//因为之前的最后一个结点是插入的,用smalllast记录
cur = next;
}
else
{
cur->next = NULL;
if (big == NULL)
{
big = cur;
}
else
{
bigLast->next = cur;
}
bigLast = cur;
cur = next;
}
}
if (small == NULL)
return big;
else
{
smallLast->next = big;
return small;
}
}
分析:定义两个链表,一个链表存放小于x的结点,一个链表存放大于等于x的结点,遍历原链表,将小于x的结点尾插到小链表中,将其他的结点尾插到大链表中,最后判断小链表是否为空,如果为空,直接返回大链表,不为空,将两个链表合并。
- 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
第一种方法:采用两个循环
/给定一个带有头结点 head 的非空单链表,
//返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
Node* MiddleNode(Node *head)
{
assert(head);
Node* cur = head;
int size = 0;
while (cur != NULL)
{
cur = cur->next;
size++;
}
Node* p = head;
for (int i = 0; i < size / 2; i++)
{
p = p->next;
}
return p;
}
分析:先求出链表的长度,定义一个指针,遍历链表的一半长度,返回指针所指的位置。
第二种方法:利用快慢指针方法
Node* MiddleNode2(Node *head)
{
Node* fast = head;
Node* slow = head;
while (fast != NULL)
{
fast = fast->next;
if (fast == NULL)
break;
slow = slow->next;
fast = fast->next;
}
return slow;
}
- 输入一个链表,输出该链表中倒数第k个结点。
第一种方法,实现两次遍历
//输入一个链表,输出该链表中倒数第k个结点。
Node* FindkToTail(Node* head, unsigned int k)
{
Node* cur = NULL;
assert(head);
cur = head;
int size = 0;
while (cur != NULL)
{
cur = cur->next;
size++;
}
if (size < k)
return NULL;
else
{
Node* p = head;
for (int i = 0; i < size - k; i++)
{
p = p->next;
}
return p;
}
}
分析:__先求出链表长度,如果链表长度小于k,直接返回NULL,否则定义一个指针,从头遍历到长度size-k,这个指针指向的就为k的位置,返回这个指针所指的位置。
第二种方法:运用前后指针
Node* FindkToTail2(Node* head, unsigned int k)
{
assert(head);
Node* front = head;
Node* back = head;
for (int i = 0; i < k; i++)
{
if (front == NULL)
return NULL;
front = front->next;
}
while (front != NULL)
{
front = front->next;
back = back->next;
}
return back;
}
分析:定义两个指针指向第一个节点,第一个指针先遍历k个,然后两个指针一起向后走,直到第一个指针到NULL,第二个指针就走了size-k长度,那么第二个指针指向的位置就为倒数第k个位置。
- 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针
第一种方法:构造一个假结点为了使每个节点都有前驱来进行删除
Node* DeleteDupLication1(Node* head)
{
Node* fackNode=(Node*)malloc(sizeof(Node));//构造一个假结点,因为要考虑第一个结点就是重复的
fackNode->next = head;
Node* pre = fackNode;
Node* p1 = head;
Node* p2 = head->next;
if (head == NULL)
return NULL;
while (p2 != NULL)//p2等于空时结束
{
if (p1->val != p2->val)
{
pre = p1;
p1 = p1->next;
p2 = p2->next;
}
else
{
while (p2 != NULL&&p2->val == p1->val)
{
p2 = p2->next;
}
//删除p1到p2之间所有的节点
pre->next = p2;
p1 = p2;
if (p2 != NULL)
{
p2 = p2->next;
}
}
}
return fackNode->next;
}
分析:定义三个指针,一个前驱指针pre,p1,p2,pre指向构造的假结点,p1指向第一个节点,p2指向第二个节点,如果链表为空,返回NULL,当结点不重复时,三个指针都往后走,一直到出现重复的节点或p2为空时;当出现重复的节点时,p2往后走,直到p2为空或者P1到p2之间出现不同的结点时,删除p1到p2之间所有的节点,让pre的下一个指向p2,p1指向p2,如果这时p2不为空,p2指向它的下一个。
第二种方法是当从第一开始重复的时候,pre为空,将head到达p2的位置,pre不为空时,不重复,pre的下一个指向p2。
Node* DeleteDupLication2(Node* head)
{
Node* pre = NULL;
Node* p1 = head;
Node* p2 = head->next;
if (head == NULL)
return NULL;
while (p2 != NULL)//p2等于空时结束
{
if (p1->val != p2->val)
{
pre = p1;
p1 = p1->next;
p2 = p2->next;
}
else
{
while (p2 != NULL&&p2->val == p1->val)
{
p2 = p2->next;
}
//删除p1到p2之间所有的节点
if (pre != NULL)
{
pre->next = p2;
}
else
{
head = p2;
}
p1 = p2;
if (p2 != NULL)
{
p2 = p2->next;
}
}
}
return head;
}
若重复的结点保留一个,则去掉pre指针。
- 判断链表是否为回文结构
//判断链表是否为回文结构
Node* FindMiddle(Node* head)
{
Node* fast = head;
Node* slow = head;
while (fast != NULL)
{
fast = fast->next;
if (fast == NULL)
break;
slow = slow->next;
fast = fast->next;
}
return slow;
}
Node* reverse(Node* head)
{
if(head==NULL)
return NULL;
Node* p=head->next;
head->next=NULL;
while(p!=NULL)
{
Node* next=p->next;
p->next=head;
head=p;
p=next;
}
return head;
}
void checkHuiwen(Node* head)
{
Node* middle = FindMiddle(head);
Node* result = reverseSList(middle);
Node* c1 = head;
Node* c2 = result;
while (c1 != NULL&&c2 != NULL)
{
if (c1->val != c2->val)
{
printf("不是回文结构!\n");
}
c1 = c1->next;
c2 = c2->next;
}
printf("是回文结构!\n");
}
分析:要判断链表是否为回文结构,首先要知道链表的中间节点,然后从中间节点开始的结点逆置,遍历两个链表,进行比较,若相同,则为回文结构,否则不是。
- 在两个链表中找第一个公共结点,公共结点后的链表是相同的。
int GetLength(Node* head)
{
Node* cur = NULL;
int length = 0;
cur = head;
while (cur != NULL)
{
cur = cur->next;
length++;
}
return length;
}
Node* publicNode(Node* head1, Node* head2)
{
Node* longer = head1;
Node* shorter = head2;
int length1 = GetLength(head1);
int length2 = GetLength(head2);
int diff = length1 - length2;
if (length2 > length1)
{
longer = head2;
shorter = head1;
diff = length2 - length1;
}
for (int i = 0; i < diff; i++)
{
longer = longer->next;
}
while (shorter&&longer != shorter)
{
longer = longer->next;
shorter = shorter->next;
}
return longer;
}
分析:两个链表不一定一样长,先计算出两个链表的长度,计算出长度差,定义两个指针,其中一个指针让它从长链表开始遍历长度差个长度,然后两个指针一起往后走,直到遇到空或者找到公共点。
- 给定一个链表,返回链表开始入环的第一个节点,如果链表无环,则返回NULL
Node* hascycle(Node* head)
{
//判断是否有环,利用快慢指针找相遇点
Node* fast = head;
Node* slow = head;
if (head == NULL)
return NULL;
do
{
fast = fast->next;
if (fast == NULL)
return NULL;
fast = fast->next;
if (fast == NULL)
return NULL;
slow = slow->next;
} while (fast != slow);//fast或者slow都为相遇点
//找环的入口点
//一个指针从起点出发,一个指针从相遇点出发,各自走一步,一定会相遇在环的入口点
Node* p1 = head;
Node* p2 = slow;
while (p1 != p2)
{
p1 = p1->next;
p2 = p2->next;
}
return p1;
}
分析:首先判断链表是否有环,利用快慢指针,定义两个指针,一个先走两步,一个再走一步,直到两个指针相遇,则有环,然后找环的入口点,一个指针从起点出发,一个指针从相遇点出发,各自走一步,一定会相遇在环的入口点,证明如下图:
- 给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何结点或空节点,要求返回这个链表的深度拷贝
Node* copyRandomList(Node* head) {
if(head==NULL)
return NULL;
Node* cur=head;
//赋值
while(cur!=NULL)
{
Node* node=(Node*)malloc(sizeof(Node));
node->val=cur->val;
node->next=cur->next;
cur->next=node;
cur=node->next;
}
//进行random操作
cur=head;
while(cur!=NULL&&cur->next!=NULL)
{
if(cur->random==NULL)
{
cur->next->random=NULL;
}
else
{
cur->next->random=cur->random->next;
}
cur=cur->next->next;
}
cur=head;
Node* pNewNode=head->next;
while(cur!=NULL&&cur->next!=NULL)
{
Node* node=cur->next;
cur->next=node->next;
if(node->next!=NULL)
{
node->next=node->next->next;
}
cur=cur->next;
}
return pNewNode;
}
分析:首先先复制一份链表每个节点的值,在将第二个链表的值放在与原链表相同的值后面,例如,原链表为1 2 3 4 NULL,1的random指针指向2,2的random指针指向1,3的random指针指向自己,4的random指针指向NULL,则操作后的链表为1 1 2 2 3 3 4 4 NULL,之后遍历链表,如果结点的random指针等于空,则这个节点的下一个结点的random指针为NULL,否则,这个节点的下一个结点的random指针指向这个节点的random指针指向的结点的下一个,即4的下一个节点4的random指向NULL,1的下一个节点1的random指向2,2的下一个节点2的random指针指向1,3的下一个节点3的random指针指向3,一次遍历两个结点;然后拆开链表,从头开始遍历操作后的链表,将复制后的结点取出来,返回新链表。