单链表基本操作的实现

本文介绍了如何使用C++实现单链表的动态初始化、数据的插入和删除、元素输出、长度计算、排序(包括快速排序)、逆置、合并有序链表等核心操作,并提供了用户友好的菜单界面。涉及数据结构的抽象与具体实现,如ElemType和Node类型,以及单链表是否带头结点的选择。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单链表基本操作的实现

【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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值