单链表基本操作的实现
【PS】: 由于笔者的能力水平有限,如果遇到相关错误或者存在歧义的地方,欢迎在下方评论区留言联系笔者,如果你觉得这篇文章对你有帮助,那么不妨动动你的小手点赞收藏转发,让更多的人看到这篇文章。你们的支持是我更新的动力(≧∇≦)/
内容
构建线性表的链式存储结构,
采用动态分配方式实现单链表的初始化,
数据的插入,
数据的删除,
输出单链表内中各元素,
求单链表的长度,
实现单链表中数据结点的按值排序,
实现单链表的逆置,
合并两个有序的单链表(有序的a表和有序的b表合并之后的结果保存在a表中)等操作。
要有能根据用户的输入来选择不同运算的菜单界面。
单链表中数据元素的类型统一抽象表示为ElemType类型,具体类型不限,可以是整型、实型、字符型、或者是自己构造的一种结构体类型。
构造单链表结点类型(Node类型),注意Node *类型与LinkList类型的区别
单链表带头结点还是不带头结点
链表上排序算法的选择
分析
这里我们统一采用带头结点的单链表。有关于单链表的知识可以去参考其他博主的讲解,这里就不多赘述。
定义单链表的结构类型:
typedef int ElemType;
typedef struct LNode {
ElemType data; //数据域
struct LNode *next; //指向下一个指针
} LNode, *LinkList;//LNode是结点,LinkList是链表。
初始化单链表
c++11
引入nullptr
,不熟悉的可以改成NULL
。
//初始化单链表
//带头节点
bool InitList(LinkList &L) {
L = (LNode *) malloc(sizeof(LNode));//分配一个头节点
if (L == nullptr)//内存不足,分配失败
return false;
L->next = nullptr; //c++11引入nullptr,可以改成null
return true;
}
头插法建立单链表
头插法,顾名思义,每次插入的元素都在头结点之后,因此在使用头插法创建的单链表,输出的元素顺序与输入的元素顺序恰好相反。有关于头插法的描述如下图所示:
详细代码如下:
//头插法建立单链表
LinkList List_HeadInsert(LinkList &L, int n) {
LNode *s;
ElemType data;
L = (LinkList) malloc(sizeof(LNode));
L->next = nullptr; //初始为空链表
for (int i = 1; i <= n; ++i) {
scanf("%d", &data);
s = (LNode *) malloc(sizeof(LNode));
s->data = data;
s->next = L->next;
L->next = s;
}
return L;
}
尾插法建立单链表
//尾插法建立单链表
LinkList List_TailInsert(LinkList &L, int n) {
L = (LinkList) malloc(sizeof(LNode));
LNode *s;
LNode *r;
r = L; //r为表尾指针
ElemType data;
for (int i = 1; i <= n; ++i) {
scanf("%d", &data);
s = (LNode *) malloc(sizeof(LNode));
s->data = data;
r->next = s;
r = s; //r指向新的表尾结点
}
r->next = nullptr; //尾结点指针置空
return L;
}
单链表的排序(快速排序)
这里我选择的是快速排序的算法,在上一篇中,我们也使用了快速排序,但是本质上还是对数组的快速排序,尔对于单链表,就不能使用之前写的代码了,那这是不是代表着在单链表中就不能使用快速排序了呢,显然并不是,在单链表中,要想实现链表基于结点数据域数据元素的大小进行排序,我们可以对结点的数据域操作而不移动结点,这样就不需要修改结点的next域,这极大的提高了代码的运行速度,因为修改数据的操作远远比修改指针操作要快得多。
快速排序(Quick Sort)的基本思想是:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
这里借鉴一下博主【啥都不会可咋办的描述】,我就不多赘述
对于数组进行快速排序的基本思想:
对于每一轮迭代, 选定一个目标元素value(通常选第一个)用来做pivot, 定义两个指针left、right分别指向数组的头和尾, left指针从左往右移动, 遇到比value大的值暂停, right指针从右往左移动, 遇到比value小的值暂停, 两个指针都停下时交换两个指针指向的元素, 然后两个指针继续移动, 直到两个指针相遇, 然后交换value与相遇处的元素, 这样通过一次迭代就把value对应的元素交换到了排序后它应该处于的正确位置, 然后以value当前所处的位置为临界点把序列分割成两部分, 对这两部分分别重复上面的操作, 以此类推.
在单链表上实现快速排序算法:
与数组很大的一个区别就是单链表不能从后往前进行遍历, 因此在做pivot的时候, 两个指针都只能从前往后进行遍历.
具体思路是: 两个指针p、q都从头结点开始往后遍历, 开始的时候p指针指向头结点(key), q指针指向p的下一个结点, q指针从前往后查找比value值小的元素, 当q指针遇到比value值小的元素的时候, p前进一位, 交换p、q指向的元素, 如果q指针遇到的元素比key大, 则p指针不动, q指针继续向前查找, 直到q指针查找到链表末端. 显然q指针比p指针移动速度快, 这就保证了p指针前面的元素都比value小, 此时p指针指向的位置就是value对应的元素在排序后的正确位置, 也就是value的位置, 因此交换value对应的元素与p指针当前指向的元素, 并以此位置作为value的临界点.
————————————————
版权声明:本文为CSDN博主「啥都不会可咋办」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/fxyburan/article/details/89382815
LNode *GetPivot(LNode *PBegin, LNode *PEnd) {
ElemType value = PBegin->data;
LNode *p = PBegin;
LNode *q = p->next;
while (q != PEnd) {
if (q->data < value) {
p = p->next;
swap(p->data, q->data);
}
q = q->next;
}
swap(p->data, PBegin->data);
return p;
}
void QuickSortLinkSort(LNode *PBegin, LNode *PEnd) {//如果带头节点那么传入方式为QuickSortLinkSort(L->next,NULL);
if (PBegin != PEnd) {
LNode *pivot = GetPivot(PBegin, PEnd);
QuickSortLinkSort(PBegin, pivot);
QuickSortLinkSort(pivot->next, PEnd);
}
}
合并单链表
我的思路是,将B链表先插入到A链表中,最后在对A链表做一次快速排序。
void LinkListMerge(LinkList &LA, LinkList &LB) {
LNode *pa,*pb;
pa=LA;
LNode *tmp;
pb = LB->next;
while (pb != nullptr) {
tmp = (LNode *) malloc(sizeof(LNode));
tmp->data = pb->data;
tmp->next = pa->next;
pa->next = tmp;
pb=pb->next;
}
QuickSortLinkSort(LA->next, nullptr);
}
单链表的逆置
LinkList LinkListReverse(LinkList &L) {
if (L->next == nullptr) //若单链表为空则直接返回
return L;
LNode *q = L, *p = L->next, *temp; //定义q指针指向前一个元素,p指针指向后一个元素,temp为临时指针
q->next = nullptr; //将即将形成的链表链尾的指针域置 NULL
while (p != nullptr) {
q->data = p->data; //将后一个元素的值赋值给前一个元素
temp = p->next; //temp暂时记录下一元素
p->next = q; //p指针此时反向指向q,即q将作为p的后继指针
q = p; //q指针后移
p = temp; //p指针后移
}
q->data = 0; //将最后形成的头结点数据域清 0
L = q; //最终 将 q赋值给 L 作为头结点
return L;
}
输出单链表
void Print(LinkList &L) {
LNode *p;
p = L->next;
if (p == nullptr)
return;
else {
while (p != nullptr) {
printf("%d ", p->data);
p = p->next;
}
}
printf("\n");
}
单链表长度
int Length(LinkList &L) {
int len = 0; //统计表长
LNode *p = L;
while (p->next != nullptr) {
p = p->next;
len++;
}
return len;
}
按位查询
//按位查找,返回第i 个元素(带头结点)
LNode *GetElem(LinkList L, int i) {
if (i < 0)
return nullptr;
LNode *p;//指针p指向当前扫描到的结点
int j = 0;//当前p指向的是第几个结点
p = L;
//L指向头结点,头结点是第0个结点(不存数据)
while (p != nullptr && j < i) {//循环找到第i 个结点
p = p->next;
j++;
}
return p;
}
按值查询
//按值查找,找到数据域==e 的结点
LNode *LocateElem(LinkList L, ElemType e) {
LNode *p = L->next;//从第1个结点开始查找数据域为e的结点
while (p != nullptr && p->data != e)
p = p->next;
return p; //找到后返回该结点指针,否则返回NULL
}
结点后插操作
bool InsertNextNode(LNode *p, ElemType e) {//后插操作
if (p == nullptr)
return false;
LNode *s;
s = (LNode *) malloc(sizeof(LNode));
if (s == nullptr) //内存分配失败
return false;
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
结点前插操作
bool InsertProirNode(LNode *p, ElemType e) {
if (p == nullptr)
return false;
LNode *s;
s = (LNode *) malloc(sizeof(LNode));
if (s == nullptr) //内存分配失败
return false;
s->next = p->next;
p->next = s;
s->data = p->data;
p->data = e;
return true;
}
单链表插入操作
Notes:
插入操作实际上可以分成两步:查找前驱结点
和结点后插操作
。分析如下:我们知道,如果想在指定位置插入元素,那么就必须要获得其前驱节点
。举个栗子:我想在第5个结点后面插入新的结点,那就必须要获得第5个节点的相关信息,因为第五个结点的next域存放的是下一个结点的地址,只有获得了该结点才能完成插入操作,然后再把该地址 存放在新插入结点的next域中,新结点的数据域存放输入的数据,最后再把新结点的地址存放在第五个结点的next域中。这样也就完成了插入操作,事实上,对应这两个操作,我们可以写两个函数来完成,提高代码复用性。即获取指定结点
操作和结点后插
操作。
//在第i个位置后插入元素e 带头结点
bool LinkInsert(LinkList &L, int i, ElemType e) {
if (i < 1)
return false;
LNode *p; //p指针指向当前扫描到的结点
int j = 0; //当前p指向的是第几个结点
p = L; //L指向头结点
while (p != nullptr && j < i - 1) { //循环找到第i-1个结点
p = p->next;
j++;
}
if (p == nullptr) //i值不合法
return false;
LNode *s;
s = (LNode *) malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
通过上述分析,我们可以对代码进行改进:查找前驱结点,可以使用上述的GetElem()
函数,传参时,只需要输入i
即可获得前驱结点,然后使用 InsertNextNode()
函数完成后去插入。
bool LinkInsert(LinkList &L, int i, ElemType e) {
if (i < 1)
return false;
LNode *p;
p = GetElem(L, i - 1);
return InsertNextNode(p, e);
}
结点删除操作
由于我们前面写过按位查询的函数,这里我们可以直接调用,提高代码的复用性。
bool ListDelete(LinkList &L, int i, ElemType &e) {
if (i < 1)
return false;
LNode *p;
p = GetElem(L, i - 1);//等价下面的代码
/*
LNode *p; //p指针指向当前扫描到的结点
int j = 0; //当前p指向的是第几个结点
p = L; //L指向头结点,头结点是第0个结点(不存数据)
while (p != nullptr && j < i - 1) { //循环找到i-1个结点
p = p->next;
j++;
}*/
if (p == nullptr) //i值不合法
return false;
if (p->next == nullptr) //第i-1个结点之后已无其他结点
return false;
LNode *q;
q = p->next; //令q指向被删除结点
e = q->data; //用e返回元素的值
p->next = q->next; //将*q结点从链中“断开”
free(q); //释放结点的存储空间
return true; //删除成功
}
函数功能测试
int mian(){
LinkList L1, L2;
List_HeadInsert(L1,6);
Print(L1);
List_TailInsert(L2,10);
Print(L2);
cout<<"L1的第三个元素为:"<<GetElem(L1,3)->data<<endl;
if(LocateElem(L2,4)->data==4)
cout<<"L2 存在数据元素 4"<<endl;
else
cout<<"不存在"<<endl;
cout<<"L1的长度为:"<<Length(L1)<<endl;
LinkInsert(L2,5,520);
cout<<"在L2第5个元素位置后插入元素520成功。"<<endl;
Print(L2);
ElemType data;
LinkDelete(L2,5,data);
cout<<"删除第5个元素:"<<data<<endl;
Print(L2);
QuickSortLinkSort(L1->next, nullptr);
cout<<"QuickSort"<<endl;
Print(L1);
cout<<"LinkListMerge"<<endl;
LinkListMerge(L1,L2);
Print(L1);
}
完整代码
菜单我就不写了,可以参考顺序表的菜单,大同小异。(如果你觉得你写的不错,可以联系我更新一下代码。)
#include <bits/stdc++.h>
using namespace std;
typedef int ElemType;
typedef struct LNode {
ElemType data;
struct LNode *next;
} LNode, *LinkList;
bool InitList(LinkList &L);
LinkList List_HeadInsert(LinkList &L, int n);
LinkList List_TailInsert(LinkList &L, int n);
LNode *GetElem(LinkList L, int i);
bool InsertNextNode(LNode *p, ElemType e);
bool LinkInsert(LinkList &L, int i, ElemType e);
bool LinkDelete(LinkList &L, int i, ElemType &e);
LNode *LocateElem(LinkList L, ElemType e);
int Length(LinkList &L);
LinkList LinkListReverse(LinkList &L);
LNode *GetPivot(LNode *PBegin, LNode *PEnd);
void QuickSortLinkSort(LNode *PBegin, LNode *PEnd);
void LinkListMerge(LinkList &LA, LinkList &LB);
void Print(LinkList &L);
char Select();
int main() {
// char k;
cout << "LinkList operation codes:\n"
"I = Initialize LinkList\n"
"T = Create a LinkList by tail method\n"
"H = Create a LinkList by head method\n"
"X = Find LinkList according to the given index\n"
"E = Find LinkList according to the given data\n"
"D = Delete the node of the specified data\n"
"L = Get the LinkList Length\n"
"R = Inverse the LinkList\n"
"S = LinkListListSort(Quick Sort)\n"
"M = Merge two LinkLists\n"
"P = Print the LinkList" << endl;
LinkList L1, L2;
List_HeadInsert(L1,6);
Print(L1);
List_TailInsert(L2,10);
Print(L2);
cout<<"L1的第三个元素为:"<<GetElem(L1,3)->data<<endl;
if(LocateElem(L2,4)->data==4)
cout<<"L2 存在数据元素 4"<<endl;
else
cout<<"不存在"<<endl;
cout<<"L1的长度为:"<<Length(L1)<<endl;
LinkInsert(L2,5,520);
cout<<"在L2第5个元素位置后插入元素520成功。"<<endl;
Print(L2);
ElemType data;
LinkDelete(L2,5,data);
cout<<"删除第5个元素:"<<data<<endl;
Print(L2);
QuickSortLinkSort(L1->next, nullptr);
cout<<"QuickSort"<<endl;
Print(L1);
cout<<"LinkListMerge"<<endl;
LinkListMerge(L1,L2);
Print(L1);
// for(;;){
// cout << "\nPlease enter SeqList operation codes:" << endl;
// cin >> k;
// switch (k) {
// char flag = Select();
//
// }
return 0;
}
//初始化单链表
//带头节点
bool InitList(LinkList &L) {
L = (LNode *) malloc(sizeof(LNode));//分配一个头节点
if (L == nullptr)//内存不足,分配失败
return false;
L->next = nullptr; //c++11引入nullptr,可以改成null
return true;
}
/*
//在第i个位置插入元素e 带头结点
bool LinkInsert(LinkList &L, int i, ElemType e) {
if (i < 1)
return false;
LNode *p; //p指针指向当前扫描到的结点
int j = 0; //当前p指向的是第几个结点
p = L; //L指向头结点
while (p != nullptr && j < i - 1) { //循环找到第i-1个结点
p = p->next;
j++;
}
if (p == nullptr) //i值不合法
return false;
LNode *s;
s = (LNode *) malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
*/
//也可以这么写
bool InsertNextNode(LNode *p, ElemType e) {//后插操作
if (p == nullptr)
return false;
LNode *s;
s = (LNode *) malloc(sizeof(LNode));
if (s == nullptr) //内存分配失败
return false;
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
bool LinkInsert(LinkList &L, int i, ElemType e) {
if (i < 1)
return false;
LNode *p;
p = GetElem(L, i - 1);
/*
LNode *p; //p指针指向当前扫描到的结点
int j = 0; //当前p指向的是第几个结点
p = L; //L指向头结点
while (p != nullptr && j < i - 1) { //循环找到第i-1个结点
p = p->next;
j++;
}
*/
return InsertNextNode(p, e);
}
//前插操作
/*
bool InsertProirNode(LNode *p, ElemType e) {
if (p == nullptr)
return false;
LNode *s;
s = (LNode *) malloc(sizeof(LNode));
if (s == nullptr) //内存分配失败
return false;
s->next = p->next;
p->next = s;
s->data = p->data;
p->data = e;
return true;
}
*/
bool LinkDelete(LinkList &L, int i, ElemType &e) {
if (i < 1)
return false;
LNode *p;
p = GetElem(L, i - 1);
/*
LNode *p; //p指针指向当前扫描到的结点
int j = 0; //当前p指向的是第几个结点
p = L; //L指向头结点,头结点是第0个结点(不存数据)
while (p != nullptr && j < i - 1) { //循环找到i-1个结点
p = p->next;
j++;
}*/
if (p == nullptr) //i值不合法
return false;
if (p->next == nullptr) //第i-1个结点之后已无其他结点
return false;
LNode *q;
q = p->next; //令q指向被删除结点
e = q->data; //用e返回元素的值
p->next = q->next; //将*q结点从链中“断开”
free(q); //释放结点的存储空间
return true; //删除成功
}
//按位查找,返回第i 个元素(带头结点)
LNode *GetElem(LinkList L, int i) {
if (i < 0)
return nullptr;
LNode *p;//指针p指向当前扫描到的结点
int j = 0;//当前p指向的是第几个结点
p = L;
//L指向头结点,头结点是第0个结点(不存数据)
while (p != nullptr && j <= i) {//循环找到第i 个结点
p = p->next;
j++;
}
return p;
}
//按值查找,找到数据域==e 的结点
LNode *LocateElem(LinkList L, ElemType e) {
LNode *p = L->next;//从第1个结点开始查找数据域为e的结点
while (p != nullptr && p->data != e)
p = p->next;
return p; //找到后返回该结点指针,否则返回NULL
}
//求表的长度
int Length(LinkList &L) {
int len = 0; //统计表长
LNode *p = L;
while (p->next != nullptr) {
p = p->next;
len++;
}
return len;
}
//头插法建立单链表
LinkList List_HeadInsert(LinkList &L, int n) {
LNode *s;
ElemType data;
L = (LinkList) malloc(sizeof(LNode));
L->next = nullptr; //初始为空链表
for (int i = 1; i <= n; ++i) {
scanf("%d", &data);
s = (LNode *) malloc(sizeof(LNode));
s->data = data;
s->next = L->next;
L->next = s;
}
return L;
}
//尾插法建立单链表
LinkList List_TailInsert(LinkList &L, int n) {
L = (LinkList) malloc(sizeof(LNode));
LNode *s;
LNode *r;
r = L; //r为表尾指针
ElemType data;
for (int i = 1; i <= n; ++i) {
scanf("%d", &data);
s = (LNode *) malloc(sizeof(LNode));
s->data = data;
r->next = s;
r = s; //r指向新的表尾结点
}
r->next = nullptr; //尾结点指针置空
return L;
}
LNode *GetPivot(LNode *PBegin, LNode *PEnd) {
ElemType value = PBegin->data;
LNode *p = PBegin;
LNode *q = p->next;
while (q != PEnd) {
if (q->data < value) {
p = p->next;
swap(p->data, q->data);
}
q = q->next;
}
swap(p->data, PBegin->data);
return p;
}
void QuickSortLinkSort(LNode *PBegin, LNode *PEnd) {//如果带头节点那么传入方式为QuickSortLinkSort(L->next,NULL);
if (PBegin != PEnd) {
LNode *pivot = GetPivot(PBegin, PEnd);
QuickSortLinkSort(PBegin, pivot);
QuickSortLinkSort(pivot->next, PEnd);
}
}
void LinkListMerge(LinkList &LA, LinkList &LB) {
LNode *pa, *pb;
pa = LA;
LNode *tmp;
pb = LB->next;
while (pb != nullptr) {
tmp = (LNode *) malloc(sizeof(LNode));
tmp->data = pb->data;
tmp->next = pa->next;
pa->next = tmp;
pb = pb->next;
}
QuickSortLinkSort(LA->next, nullptr);
}
LinkList LinkListReverse(LinkList &L) {
if (L->next == nullptr) //若单链表为空则直接返回
return L;
LNode *q = L, *p = L->next, *temp; //定义q指针指向前一个元素,p指针指向后一个元素,temp为临时指针
q->next = nullptr; //将即将形成的链表链尾的指针域置 NULL
while (p != nullptr) {
q->data = p->data; //将后一个元素的值赋值给前一个元素
temp = p->next; //temp暂时记录下一元素
p->next = q; //p指针此时反向指向q,即q将作为p的后继指针
q = p; //q指针后挪
p = temp; //p指针后挪
}
q->data = 0; //将最后形成的头结点数据域清 0
L = q; //最终 将 q赋值给 L 作为头结点
return L;
}
void Print(LinkList &L) {
LNode *p;
p = L->next;
if (p == nullptr)
return;
else {
while (p != nullptr) {
printf("%d ", p->data);
p = p->next;
}
}
printf("\n");
}
char Select() {
char ch;
cout << "Please select the SeqList you want to operate on(input A or B):" << endl;
cin >> ch;
return ch;
}