栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

数据结构与算法(C语言)——单链表

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

数据结构与算法(C语言)——单链表

Part1:单链表基础知识

 

        单链表是通过一组任意的存储单元来存储线性表中的数据元素的。每个元素包括两部分:存放数据元素的域值为数据域data,存放其后继地址的域称为指针域next。

单链表的结点结构

        显然,除第一个元素外,每个元素都有一个直接前驱。为方便操作,一般设一个头指针H指向第一个结点;除最后一个元素外,每个元素都有一个直接后继。为方便操作,指定线性表中最后一个结点的指针域为"空"(NULL)。

        单链表的存储结构用C语言描述如下:

typedef int ElemType;     // 自定义链表的数据元素为整数。

typedef struct LNode
{
    ElemType data;       // 存放结点的数据元素。
    struct LNode* next;  // 指向下一个结点的指针。
}LNode, * LinkList;

//定义头指针变量
LinkList H;

        上面的LNode是结点的类型,LinkList是指向LNode类型结点的指针类型。

        通常用头指针来标识一个单链表,如单链表H。是指该链表的第一个结点的地址放在了指针变量H中,头指针为NULL,则表示一个空表。

 Part2:代码解释

        关于单链表的操作,主要掌握以下几点:

单链表的基本操作​​​​​​
 1.准备工作
#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include 

typedef int ElemType;     // 自定义链表的数据元素为整数。

typedef struct LNode
{
    ElemType data;       // 存放结点的数据元素。
    struct LNode* next;  // 指向下一个结点的指针。
}LNode, * LinkList;


int main()
{
    LinkList LL = NULL; // 声明链表指针变量。

    LL = InitList1();      // Way1:初始化链表

    // InitList2(&LL);     // Way2:初始化链表,传入指针变量LL的地址。

    ElemType ee;      // 创建一个数据元素。

    return0;
}

        这部分就是单链表的存储结构,LNode是结点的类型,LinkList是指向LNode类型结点的指针类型。

2.初始化链表 2.1Way1
// 初始化链表LL,返回值:失败返回NULL,成功返回头结点的地址。
LNode* InitList1()
{
    LNode* head = (LNode*)malloc(sizeof(LNode));  // 分配头结点。

    if (head == NULL) return NULL;  // 内存不足,返回失败。

    head->next = NULL;  // 头结点的下一结点暂时不存在,置空。

    return head;
}

        初始化链表,函数的返回类型为LNode*,即初始化成功,就返回头结点的地址。malloc动态内存开辟,开辟多大空间呢?用sizeof(LNode)进行计算;强制类型转换为LNode*,赋给头结点head,头结点的类型也是LNode*。

        如果头结点为空,则说明内存不足,返回失败;否则,就将头结点的下一个节点置空,返回头结点的地址。

2.2Way2
// 传入指针变量的地址的方法。
// 初始化链表,返回值:0-失败;1-成功。
int InitList2(LinkList* LL)
{
    // 在本函数中,LL是指针的指针,用于存放指针的地址。

    if (*LL != NULL) { printf("链表LL已存在,在初始化之前请先释放它。n"); return 0; }

    LNode* head = (LNode*)malloc(sizeof(LNode));  // 分配头结点。

    if (head == NULL) return 0;  // 内存不足,返回失败。

    head->next = NULL;  // 头结点的下一结点暂时不存在,置空。

    *LL = head;

    return 1;
}

        第二种初始化方法,直接将链表的指针变量传入,用一个指针接受,则在本函数中,LL就是指针的指针,用于存放指针的地址。函数内的操作与第一种方法类似,不同之处在于第一种方法是返回头结点的地址,在main函数中赋给LL;而该方法则是在函数内部就将头结点地址赋给LL,返回1表示初始化成功。

3.求链表长度
// 求链表的长度,返回值:>=0-表LL结点的个数。
int  LengthList(LinkList LL)
{
    if (LL == NULL) { printf("链表LL不存在。n"); return 0; } // 判断链表是否存在。

    LNode* pp = LL->next;  // 头结点不算,从第1个结点开始。

    int length = 0;

    while (pp != NULL) 
    { 
        pp = pp->next;
        length++;
    }

    return length;
}
  1. 判断链表是否存在;
  2. 创建指针指向链表第一个结点,同时创建临时变量,指针后移的同时临时变量自增;
  3. 返回链表的长度
4.插入元素 4.1在任意位置插入元素
// 在链表LL的第ii个位置插入元素ee,返回值:0-失败;1-成功。 
int InsertList(LinkList LL, unsigned int ii, ElemType* ee)
{
    if ((LL == NULL) || (ee == NULL)) { printf("链表LL或元素ee不存在。n"); return 0; } // 判断表和元素是否存在。

    // 判断插入位置是否合法
    if (ii < 1) { printf("插入位置(%d)不合法,应该在大于0。n", ii); return 0; }

    // 要在位序ii插入结点,必须找到ii-1结点。
    LNode* pp = LL;  // 指针pp指向头结点,逐步往后移动,如果为空,表示后面没结点了。
    int kk = 0;      // kk指向的是第几个结点,从头结点0开始,pp每向后移动一次,kk就加1。

    while ((pp != NULL) && (kk < ii - 1))
    {
        pp = pp->next; kk++;

        // printf("pp=%p,kk=%dn",pp,kk);
    }

    if (pp == NULL) { printf("位置(%d)不合法,超过了表长。n", ii); return 0; }

    LNode* tmp = (LNode*)malloc(sizeof(LNode));  // 分配一个结点。
    if (tmp == NULL) return 0;  // 内存不足,返回失败。

    // 考虑数据元素为结构体的情况,这里采用了memcpy的方法而不是直接赋值。
    memcpy(&tmp->data, ee, sizeof(ElemType));

    // 处理next指针。
    tmp->next = pp->next;
    pp->next = tmp;

    return 1;

    ///
    // 以上代码可以用以下代码代替。
    // LNode *pp=LocateNode(LL,ii-1);  
    // return InsertNextNode(pp,ee);
    ///
}
  1. 首先,保证传入的链表和元素存在;
  2. 其次,插入的位置必须大于0;
  3. 注意:在单链表上插入、删除一个结点时,必须知道其前驱节点;所以让指针pp指向头结点,此时kk为0;pp逐步后移,每后移一次,kk自增1。直到找到要插入位置的前驱结点,就跳出循环。
  4. 此时进行判断,如果pp是空空指针,说明要插入的位置超过表长,非法;
  5. 跳过层层判断,此时我们找到了要插入位置的前一个结点,我们分配一个新结点,data数据域放我们要插入的元素,next指针进行相对应的处理即可。由下图,可更容易理解这两行代码。
  6. 插入成功,最后返回1,插入元素的函数就完成了!

next指针处理方式图解:

假设在第2个位置插入元素
 4.2在表头插入元素
// 在链表LL的头部插入元素ee,返回值:0-失败;1-成功。
int  PushFront(LinkList LL, ElemType* ee)
{
    return InsertList(LL, 1, ee);
}

        这个容易,只需将插入的位置传入参数1即可。

4.3在表尾插入元素
// 在链表LL的尾部插入元素ee,返回值:0-失败;1-成功。
int PushBack(LinkList LL, ElemType* ee)
{
    if ((LL == NULL) || (ee == NULL)) { printf("链表LL或元素ee不存在。n"); return 0; } // 判断表和元素是否存在。

    LNode* pp = LL;  // 从头结点开始。

    // 找到最后一个结点。
    while (pp->next != NULL) pp = pp->next;

    LNode* tmp = (LNode*)malloc(sizeof(LNode));  // 分配一个结点。
    if (tmp == NULL) return 0;  // 内存不足,返回失败。

    // 考虑数据元素为结构体的情况,这里采用了memcpy的方法而不是直接赋值。
    memcpy(&tmp->data, ee, sizeof(ElemType));

    // 处理next指针。
    tmp->next = NULL;
    pp->next = tmp;

    return 1;
}
  1. 由于不知道链表的长度,所以不能使用已有的InsertList函数。
  2. 首先,保证传入的链表和元素存在;
  3. 然后,创建指针pp,指向头结点并不断后移,直到找到最后一个结点。
  4. 分配一个新的结点,data数据域放我们要插入的元素,将新结点的next域置为NULL,并让原来最后一个结点的next域存储新的结点地址。
  5. 插入成功,返回1即可。

        若结合已有的求链表长度的函数,则该函数也可以改写为:

// 在链表LL的尾部插入元素ee,返回值:0-失败;1-成功。
int PushBack(LinkList LL, ElemType* ee)
{

    int len = LengthList(LL);
    return InsertList(LL, len+1, ee);
}

        这里注意传入的第二个参数是len+1,如果传入len,那么在链表的最后一个位置插入元素,则原先的表尾元素会向后移一位,就变成在倒数第二个位置插入元素了。

5.删除元素 5.1在任意位置删除元素
// 删除链表LL中的第ii个结点,返回值:0-位置ii不合法;1-成功。
int  DeleteNode(LinkList LL, unsigned int ii)
{
    if (LL == NULL) { printf("链表LL不存在。n"); return; } // 判断链表是否存在。

    // 判断删除位置是否合法
    if (ii < 1) { printf("删除位置(%d)不合法,应该在大于0。n", ii); return 0; }

    // 要删除位序ii结点,必须找到ii-1结点。
    LNode* pp = LL;  // 指针pp指向头结点,逐步往后移动,如果为空,表示后面没结点了。
    int kk = 0;      // kk指向的是第几个结点,从头结点0开始,pp每向后移动一次,kk就加1。

    while ((pp != NULL) && (kk < ii - 1))
    {
        pp = pp->next; kk++;
    }

    if (pp->next == NULL) { printf("位置(%d)不合法,超过了表长。n", ii); return 0; }

    LNode* tmp = pp->next;  // tmp为将要删除的结点。
    pp->next = pp->next->next;   // 写成pp->next=tmp->next更简洁。

    free(tmp);

    return 1;
}

        参考向链表中插入元素,对next指针的处理方式如下:

假设在第2个位置删除元素
 5.2在链表头部删除元素
// 删除链表LL中第一个结点,返回值:0-位置不合法;1-成功。
int PopFront(LinkList LL)
{
    return DeleteNode(LL, 1);
}
5.3在链表尾部删除元素
// 删除链表LL中最后一个结点,返回值:0-位置不合法;1-成功。
int PopBack(LinkList LL)
{
    if (LL == NULL) { printf("链表LL不存在。n"); return 0; } // 判断表和元素是否存在。

    // 必须加上这个判断,否则下面的循环pp->next->next不成立。
    if (LL->next == NULL) { printf("链表LL为空,没有尾结点。n"); return 0; } // 判断表是否为空。

    // 要删除最后一个结点,必须找到最后一个结点的前一个结点。

    LNode* pp = LL;  // 从第0个结点开始。

    // 找到倒数第二个结点(包括头结点)。
    while (pp->next->next != NULL) pp = pp->next;

    // 释放最后一个结点。
    free(pp->next);
    pp->next = NULL;

    return 1;
}

由于表长未知,任从头结点开始,找到倒数第二个结点,将最后一个结点释放,并将倒数第二个结点的next指针域置为NULL。当然,如果结合已有的求链表长度的函数,也可以简化该函数。

6.查找某一位置的元素及地址
// 获取链表中第ii个结点,成功返回结点的地址,失败返回空。
// 注意,ii可以取值为0,表示头结点。
LNode* LocateNode(LinkList LL, unsigned int ii)
{
    if (LL == NULL) { printf("链表LL不存在。n"); return NULL; } // 判断表和元素是否存在。

    LNode* pp = LL;  // 指针pp指向头结点,逐步往后移动,如果为空,表示后面没结点了。
    int kk = 0;      // kk指向的是第几个结点,从头结点0开始,pp每向后移动一次,kk就加1。

    while ((pp != NULL) && (kk < ii))
    {
        pp = pp->next; kk++;
    }

    if (pp == NULL) { printf("位置(%d)不合法,超过了表长。n", ii); return NULL; }

    return pp;
}

也是设置一个结点pp,不断向后找;当kk=0时,进入while循环,pp已经指向了第1个结点;kk=1时,pp则指向第2个结点。依此类推,要找ii位置的结点,则最后一次循环时kk=i-1。

7.查找某一元素的地址
// 查找元素ee在链表LL中的结点地址,如果没找到返回NULL,否则返回结点的地址。
LNode* LocateElem(LinkList LL, ElemType* ee)
{
    LNode* pp = LL->next;  // 从第1个数据结点开始。

    while (pp != NULL)
    {
        // 如果数据元素是结构体,以下代码要修改。
        if (pp->data == *ee) return pp;

        pp = pp->next;
    }

    return NULL;
}

        查找某一元素的地址,从第一个结点开始,依次向后,如果pp->data==*ee,说明已经找到相应的元素,就返回pp的值。当然,也可以从头结点开始查找。

8.根据结点插入元素

无论插入还是删除元素,都要找到该结点的前一个结点!

8.1在指定结点后插入元素
// 在指定结点pp之后插入元素ee,返回值:0-失败;1-成功。
int InsertNextNode(LNode* pp, ElemType* ee)
{
    if (pp == NULL) { printf("结点pp不存在。n"); return 0; }

    LNode* tmp = (LNode*)malloc(sizeof(LNode));
    if (tmp == NULL) return 0;

    memcpy(&tmp->data, ee, sizeof(ElemType));
    tmp->next = pp->next;
    pp->next = tmp;

    return 1;
}

 只要是插入元素,一定需要分配新的内存空间给要插入的结点(即这里的tmp)。图解如下:

假设在第2个结点后插入元素

8.2在指定节点前插入元素
// 在指定结点pp之前插入元素ee,返回值:0-失败;1-成功。
int InsertPriorNode(LNode* pp, ElemType* ee)
{
    if (pp == NULL) { printf("结点pp不存在。n"); return 0; }

    // 在指定结点pp之前插入采用偷梁换柱的方法:
    // 1、分配一个新的结点;
    // 2、把pp结点的数据和指针复制到新结点中。
    // 3、把待插入元素的数据存入pp结点中。

    LNode* tmp = (LNode*)malloc(sizeof(LNode));
    if (tmp == NULL) return 0;

    // 把pp结点的数据和指针复制到tmp中。
    memcpy(&tmp->data, &pp->data, sizeof(ElemType));
    tmp->next = pp->next;

    // 把待插入的元素存入pp中。
    memcpy(&pp->data, ee, sizeof(ElemType));
    pp->next = tmp;

    return 1;
}

因为我们只能找到指定结点之后的元素,而无法找到指定结点之前的元素,所以在指定结点之前插入元素,采用偷梁换柱的方法。图解如下:

假设在第2个结点前插入元素
 9.删除指定元素
//删除指定元素,返回值:0-失败;1-成功。
int DeleteElem(LinkList LL, ElemType* ee)
{
    if (LL == NULL) { printf("链表LL不存在。n"); return; } // 判断链表是否存在。

    // 要删除位序ii结点,必须找到ii-1结点。
    LNode* pp = LL->next;  // 指针pp指向头结点,逐步往后移动,如果为空,表示后面没结点了。
    int kk = 0;      // kk指向的是第几个结点,从头结点0开始,pp每向后移动一次,kk就加1。

    while ((pp != NULL) && (pp->next->data!=(*ee)))
    {
        pp = pp->next;
    }

    // 注意,以下行的代码与视频中的不一样,视频中的是 if ( pp==NULL ),有bug。
    if (pp->next == NULL) { printf("元素不存在,无法删除!n"); return 0; }

    LNode* tmp = pp->next;  // tmp为将要删除的结点。
    pp->next = tmp->next; 

    free(tmp);

    return 1;
}

参考按结点删除元素,不难理解。

10.翻转链表
// 把链表pp结点之后的结点原地逆置(反转),返回值:0-失败;1-成功。
void ReverseList(LNode* pp)
{
    LNode* ss;      // 当前结点。
    LNode* ssnext;  // 当前结点的下一结点。

    ss = pp->next;    // 从pp结点之后的结点开始反转。

    pp->next = NULL;  // pp->next指向空。

    while (ss != NULL)
    {
        ssnext = ss->next;  // 保留ss下一结点的地址。

        // 以下两行相当于在pp之后插入ss结点。
        ss->next = pp->next;
        pp->next = ss;

        ss = ssnext;  // ss结点后移。
    }
}

把pp结点之后的结点进行翻转,相当于把pp结点之后的元素逐个紧跟在pp结点后插入,其逻辑类似于:

 while循环图解:

(假设将第2个结点之后的结点进行翻转)

注意:第一次循环,pp->next=NULL; 

第一次循环
第二次循环
第三次循环

循环部分不容易理解,可以结合图例进行思考。

11.合并链表
// 采用归并的方法,将两个升序的链表La和Lb,合并成一个升序的链表Lc。
int MergeList(LinkList La, LinkList Lb, LinkList Lc)
{
    if ((La == NULL) || (Lb == NULL) || (Lc == NULL)) { printf("表La、Lb、Lc至少有一个不存在。n"); return 0; }

    La = La->next;    //从第一个结点开始
    Lb = Lb->next;    //从第一个结点开始

    LNode* pp;

    // 把La和Lb合并到Lc中。
    while ((La != NULL) && (Lb != NULL))
    {
        // 取La和Lb的较小者。
        if (La->data <= Lb->data)
        {
            pp = La; La = La->next;
        }
        else
        {
            pp = Lb; Lb = Lb->next;
        }

        // 把较小者追加到Lc中。
        Lc->next = (LNode*)malloc(sizeof(LNode));  // 分配一个新结点。
        Lc = Lc->next;
        memcpy(&Lc->data, &pp->data, sizeof(ElemType));
        Lc->next = NULL;
    }

    // 把链表La其它的元素追加到Lc中。
    while (La != NULL)
    {
        Lc->next = (LNode*)malloc(sizeof(LNode));  // 分配一个新结点。
        Lc = Lc->next;
        memcpy(&Lc->data, &La->data, sizeof(ElemType));
        Lc->next = NULL;
        La = La->next;
    }

    // 把链表Lb其它的元素追加到Lc中。
    while (Lb != NULL)
    {
        Lc->next = (LNode*)malloc(sizeof(LNode));  // 分配一个新结点。
        Lc = Lc->next;
        memcpy(&Lc->data, &Lb->data, sizeof(ElemType));
        Lc->next = NULL;
        Lb = Lb->next;
    }

    return 1;
}

        结合前面顺序表的合并操作,不难理解链表的合并。

12.清空链表
// 清空链表。
void ClearList(LinkList LL)
{
    // 清空链表LL是指释放链表全部的结点,但不包括头结点。
    if (LL == NULL) { printf("链表LL不存在。n"); return; } // 判断链表是否存在。

    LNode* tmp1;
    LNode* tmp2 = LL->next;  // 保留头结点,从头结点的下一个结点开始释放。

    while (tmp2 != NULL)
    {
        tmp1 = tmp2->next;
        free(tmp2);
        tmp2 = tmp1;
    }

    LL->next = NULL; // 这行代码一定不能少,否则会留下野指针。

   

       对于清空链表,因为传入的是链表的头结点,所以需要创建两个指针变量,rmp1进行移动,tmp2存储链表的第一个结点。tmp1不断向后移动,找下一个结点;tmp2则负责释放相应的内存空间。需要注意的是在最后需要把LL->next置为空,防止野指针出现。

13.销毁链表

Way1:

// 销毁链表LL。
void DestroyList1(LinkList LL)
{
    // 销毁链表LL是指释放链表全部的结点,包括头结点。
    LNode* tmp;

    while (LL != NULL)
    {
        tmp = LL->next;  // tmp保存下一结点的地址。
        free(LL);      // 释放当前结点。
        LL = tmp;        // LL指针移动到下一结点。
    }

    // LL=NULL;   // LL在本函数中相当于局部变量,就算置空了也不会影响调用者传递的LL,所以LL=NULL没有意义。

    return;
}

        对于销毁链表,不需要额外的指针变量存储链表第1个结点的信息,所以只需要一个指针变量,不断向后移动,并释放其所指向的内存空间。

Way2:

// 销毁链表LL。
void DestroyList2(LinkList* LL)
{
    // 如果函数的参数是指针的指针,可以启用以下代码。
    LNode* tmp1, * tmp2;

    tmp1 = *LL;

    while (tmp1 != NULL)
    {
        tmp2 = tmp1->next; // tmp保存下一结点的地址。
        free(tmp1);      // 释放当前结点。
        tmp1 = tmp2;       // LL指针移动到下一结点。
    }

    *LL = NULL;  // 把链表的指针置为空,表示链表不存在了。

    return;
}

        传入指针变量。

Part3:完整代码实现


#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include 

typedef int ElemType;     // 自定义链表的数据元素为整数。

typedef struct LNode
{
    ElemType data;       // 存放结点的数据元素。
    struct LNode* next;  // 指向下一个结点的指针。
}LNode, * LinkList;


void menu()
{
    printf("*************************************************n");
    printf("***** 0.Exit            1.LengthList      *******n");
    printf("***** 2.Insert          3.PushFront       *******n");
    printf("***** 4.PushBack        5.Delete          *******n");
    printf("***** 6.PopFront        7.PopBack         *******n");
    printf("***** 8.IsEmpty         9.LocateNode      *******n");
    printf("*****10.LocateElem     11.MergeList       *******n");
    printf("*****12.InsertNextNode 13.InsertPriorNode *******n");
    printf("*****14.ReverseList    15.DeleteElem      *******n");
    printf("*****16.ClearList                         *******n"); 
    printf("*************************************************n");
}

// 初始化链表LL,返回值:失败返回NULL,成功返回头结点的地址。
LNode* InitList1();

// 传入指针变量的地址的方法。
// 初始化链表,返回值:0-失败;1-成功。
int InitList2(LinkList* LL);


// 销毁链表LL。
void DestroyList1(LinkList LL);

// 销毁链表LL。
// 传入指针的地址的方法。
void DestroyList2(LinkList* LL);


// 清空链表。
void ClearList(LinkList LL);

// 在链表LL的第ii个位置插入元素ee,返回值:0-失败;1-成功。
int  InsertList(LinkList LL, unsigned int ii, ElemType* ee);

// 打印链表中全部的元素。
void PrintList(LinkList LL);

// 在链表LL的头部插入元素ee,返回值:0-失败;1-成功。
int  PushFront(LinkList LL, ElemType* ee);

// 在链表LL的尾部插入元素ee,返回值:0-失败;1-成功。
int  PushBack(LinkList LL, ElemType* ee);

// 删除链表LL中的第ii个结点,返回值:0-位置ii不合法;1-成功。
int  DeleteNode(LinkList LL, unsigned int ii);

// 删除链表LL中第一个结点,返回值:0-位置不合法;1-成功。
int PopFront(LinkList LL);

// 删除链表LL中最后一个结点,返回值:0-位置不合法;1-成功。
int PopBack(LinkList LL);

// 求链表的长度,返回值:>=0-表LL结点的个数。
int  LengthList(LinkList LL);

// 判断链表是否为空,返回值:0-非空或失败,1-空。
int IsEmpty(LinkList LL);

// 获取链表中第ii个结点,成功返回结点的地址,失败返回空。
// 注意,ii可以取值为0,表示头结点。
LNode* LocateNode(LinkList LL, unsigned int ii);

// 查找元素ee在链表LL中的结点地址,如果没找到返回NULL,否则返回结点的地址。
LNode* LocateElem(LinkList LL, ElemType* ee);

// 在指定结点pp之后插入元素ee,返回值:0-失败;1-成功。
int InsertNextNode(LNode* pp, ElemType* ee);

// 在指定结点pp之前插入元素ee,返回值:0-失败;1-成功。
int InsertPriorNode(LNode* pp, ElemType* ee);

//删除指定元素ee,返回值:0-失败;1-成功。
int DeleteElem(LNode* pp, ElemType* ee);

// 采用归并的方法,将两个升序的链表La和Lb,合并成一个升序的链表Lc。
int MergeList(LinkList La, LinkList Lb, LinkList Lc);

// 把链表pp结点之后的结点原地逆置(反转),返回值:0-失败;1-成功。
void ReverseList(LNode* pp);



int main()
{
    LinkList LL = NULL; // 声明链表指针变量。

    LL = InitList1();      // Way1:初始化链表

    // InitList2(&LL);     // Way2:初始化链表,传入指针变量LL的地址。


    //printf("LL=%pn", LL);

    ElemType ee;      // 创建一个数据元素。

    printf("在表中插入元素(1、2、3、4、5、6、7、8、9、10)。n");
    ee = 1;  InsertList(LL, 1, &ee);
    ee = 2;  InsertList(LL, 1, &ee);
    ee = 3;  InsertList(LL, 1, &ee);
    ee = 4;  InsertList(LL, 1, &ee);
    ee = 5;  InsertList(LL, 1, &ee);
    ee = 6;  InsertList(LL, 1, &ee);
    ee = 7;  InsertList(LL, 1, &ee);
    ee = 8;  InsertList(LL, 1, &ee);
    ee = 9;  InsertList(LL, 1, &ee);
    ee = 10; InsertList(LL, 1, &ee);
    PrintList(LL);
    int input = 0; 
    int count = 0;
    menu();
    do
    {
        count++;
        if (count % 5 == 0)
        {
            menu();
        }
        printf("请选择:");
        scanf("%d", &input);
        switch (input)
        {
        case 0:
            DestroyList1(LL);
            LL = NULL;
            break;
        case 1:
            int length = LengthList(LL);
            printf("链表的长度为:%dn", length);
            break;
        case 2:
            int x = 0; int y = 0;
            printf("依次输入插入的位置及插入的元素:>");
            scanf("%d %d", &x, &y);
            printf("在第%d个位置插入元素%dn", x, y);
            ee = y; InsertList(LL, x, &ee);
            PrintList(LL);
            break;
        case 3:
            int w = 0;
            printf("在表头插入元素:>");
            scanf("%d", &w);
            ee = w; PushFront(LL, &ee);
            PrintList(LL);
            break;
        case 4:
            int u = 0;
            printf("在表尾插入元素:>");
            scanf("%d", &u);
            ee = u; PushBack(LL, &ee);
            PrintList(LL);
            break;
        case 5:
            int b = 0;
            printf("删除表中第几个元素:>");
            scanf("%d", &b);
            DeleteNode(LL, b);
            PrintList(LL);
            break;
        case 6:
            printf("删除第一个元素。n");
            PopFront(LL);
            PrintList(LL);
            break;
        case 7:
            printf("删除表尾元素。n");
            PopBack(LL);
            PrintList(LL);
            break;
        case 8:
            if (IsEmpty(&LL) == 1)
                printf("链表为空。n");
            else
                printf("链表不为空。n");
            break;
        case 9:
            LNode * tma;
            int d = 0;
            printf("输入你要查找第几个结点:>");
            scanf("%d", &d);
            if ((tma = LocateNode(LL, d)) != NULL)
                printf("第%d个结点的地址是=%p,ee=%dn", d,tma,tma->data);
            break;
        case 10:
            LNode * tmb;
            int s = 0;
            printf("输入你要查找的元素值:>");
            scanf("%d", &s);
            ee = s;
            if ((tmb = LocateElem(LL, &ee)) != NULL)
                printf("元素值为%d的地址是=%pn", s, tmb);
            else
                printf("未找到该元素!n");
            break;
        case 11:
            LinkList La, Lb, Lc;
            
                La=InitList1();
                Lb=InitList1();
                Lc=InitList1();
            
                ee=1;  PushBack(Lb, &ee);
                ee=2;  PushBack(La, &ee);
                ee=3;  PushBack(Lb, &ee);
                ee=4;  PushBack(Lb, &ee);
                ee=5;  PushBack(La, &ee);
                ee=6;  PushBack(Lb, &ee);
                ee=7;  PushBack(Lb, &ee);
                ee=8;  PushBack(Lb, &ee);
                ee=9;  PushBack(La, &ee);
                ee=10; PushBack(La, &ee);
                printf("表La的内容为:>");
                PrintList(La);
                printf("表Lb的内容为:>");
                PrintList(Lb);
            
                MergeList(La,Lb,Lc);
                printf("表Lc的内容为:>");
                PrintList(Lc);
            
                DestroyList1(La); La=NULL;
                DestroyList1(Lb); Lb=NULL;
                DestroyList1(Lc); Lc=NULL;
            break;
        case 12:
            int p = 0; int q = 0;
            printf("请输入指定的结点和要插入的元素:>");
            scanf("%d %d", &p, &q);
            ee = p;
            LNode * tmp = LocateElem(LL, &ee);
            ee = q;
            InsertNextNode(tmp, &ee);
            PrintList(LL);
            break;
        case 13:
            int h = 0; int i = 0;
            printf("请输入指定的结点和要插入的元素:>");
            scanf("%d %d", &h, &i);
            ee = h;
            LNode* rac = LocateElem(LL, &ee);
            ee = i;
            InsertPriorNode(rac, &ee);
            PrintList(LL);
            break;
        case 14:
            printf("从这个结点之后原地逆置:>");
            int f = 0;
            scanf("%d", &f);
            ee = f;
            LNode* tme = LocateElem(LL, &ee);
            ReverseList(tme);
            PrintList(LL);
            break;
        case 15:
            int c = 0;
            printf("输入要删除的元素:>");
            scanf("%d", &c);
            ee = c;
            DeleteElem(LL, &ee);
            PrintList(LL);
            break;
        case 16:
            ClearList(LL);
            PrintList(LL);
            break;
         default:
            printf("选择错误n");
            break;
        }
    } while (input);

    return 0;
}


// 初始化链表LL,返回值:失败返回NULL,成功返回头结点的地址。
LNode* InitList1()
{
    LNode* head = (LNode*)malloc(sizeof(LNode));  // 分配头结点。

    if (head == NULL) return NULL;  // 内存不足,返回失败。

    head->next = NULL;  // 头结点的下一结点暂时不存在,置空。

    return head;
}

// 传入指针变量的地址的方法。
// 初始化链表,返回值:0-失败;1-成功。
int InitList2(LinkList* LL)
{
    // 在本函数中,LL是指针的指针,用于存放指针的地址。

    if (*LL != NULL) { printf("链表LL已存在,在初始化之前请先释放它。n"); return 0; }

    LNode* head = (LNode*)malloc(sizeof(LNode));  // 分配头结点。

    if (head == NULL) return 0;  // 内存不足,返回失败。

    head->next = NULL;  // 头结点的下一结点暂时不存在,置空。

    *LL = head;

    return 1;
}



// 销毁链表LL。
void DestroyList1(LinkList LL)
{
    // 销毁链表LL是指释放链表全部的结点,包括头结点。
    LNode* tmp;

    while (LL != NULL)
    {
        tmp = LL->next;  // tmp保存下一结点的地址。
        free(LL);      // 释放当前结点。
        LL = tmp;        // LL指针移动到下一结点。
    }

    // LL=NULL;   // LL在本函数中相当于局部变量,就算置空了也不会影响调用者传递的LL,所以LL=NULL没有意义。

    return;
}

// 销毁链表LL。
void DestroyList2(LinkList* LL)
{
    // 如果函数的参数是指针的指针,可以启用以下代码。
    LNode* tmp1, * tmp2;

    tmp1 = *LL;

    while (tmp1 != NULL)
    {
        tmp2 = tmp1->next; // tmp保存下一结点的地址。
        free(tmp1);      // 释放当前结点。
        tmp1 = tmp2;       // LL指针移动到下一结点。
    }

    *LL = NULL;  // 把链表的指针置为空,表示链表不存在了。

    return;
}



// 清空链表。
void ClearList(LinkList LL)
{
    // 清空链表LL是指释放链表全部的结点,但不包括头结点。
    if (LL == NULL) { printf("链表LL不存在。n"); return; } // 判断链表是否存在。

    LNode* tmp1;
    LNode* tmp2 = LL->next;  // 保留头结点,从头结点的下一个结点开始释放。

    while (tmp2 != NULL)
    {
        tmp1 = tmp2->next;
        free(tmp2);
        tmp2 = tmp1;
    }

    LL->next = NULL; // 这行代码一定不能少,否则会留下野指针。

    return;
}



// 在链表LL的第ii个位置插入元素ee,返回值:0-失败;1-成功。 
int InsertList(LinkList LL, unsigned int ii, ElemType* ee)
{
    if ((LL == NULL) || (ee == NULL)) { printf("链表LL或元素ee不存在。n"); return 0; } // 判断表和元素是否存在。

    // 判断插入位置是否合法
    if (ii < 1) { printf("插入位置(%d)不合法,应该在大于0。n", ii); return 0; }

    // 要在位序ii插入结点,必须找到ii-1结点。
    LNode* pp = LL;  // 指针pp指向头结点,逐步往后移动,如果为空,表示后面没结点了。
    int kk = 0;      // kk指向的是第几个结点,从头结点0开始,pp每向后移动一次,kk就加1。

    while ((pp != NULL) && (kk < ii - 1))
    {
        pp = pp->next; kk++;

        // printf("pp=%p,kk=%dn",pp,kk);
    }

    if (pp == NULL) { printf("位置(%d)不合法,超过了表长。n", ii); return 0; }

    LNode* tmp = (LNode*)malloc(sizeof(LNode));  // 分配一个结点。
    if (tmp == NULL) return 0;  // 内存不足,返回失败。

    // 考虑数据元素为结构体的情况,这里采用了memcpy的方法而不是直接赋值。
    memcpy(&tmp->data, ee, sizeof(ElemType));

    // 处理next指针。
    tmp->next = pp->next;
    pp->next = tmp;

    return 1;

    ///
    // 以上代码可以用以下代码代替。
    // LNode *pp=LocateNode(LL,ii-1);  
    // return InsertNextNode(pp,ee);
    ///
}

// 删除链表LL中的第ii个结点,返回值:0-位置ii不合法;1-成功。
int  DeleteNode(LinkList LL, unsigned int ii)
{
    if (LL == NULL) { printf("链表LL不存在。n"); return; } // 判断链表是否存在。

    // 判断删除位置是否合法
    if (ii < 1) { printf("删除位置(%d)不合法,应该在大于0。n", ii); return 0; }

    // 要删除位序ii结点,必须找到ii-1结点。
    LNode* pp = LL;  // 指针pp指向头结点,逐步往后移动,如果为空,表示后面没结点了。
    int kk = 0;      // kk指向的是第几个结点,从头结点0开始,pp每向后移动一次,kk就加1。

    while ((pp != NULL) && (kk < ii - 1))
    {
        pp = pp->next; kk++;
    }

    if (pp->next == NULL) { printf("位置(%d)不合法,超过了表长。n", ii); return 0; }

    LNode* tmp = pp->next;  // tmp为将要删除的结点。
    pp->next = pp->next->next;   // 写成pp->next=tmp->next更简洁。

    free(tmp);

    return 1;
}

// 在链表LL的头部插入元素ee,返回值:0-失败;1-成功。
int  PushFront(LinkList LL, ElemType* ee)
{
    return InsertList(LL, 1, ee);
}

// 在链表LL的尾部插入元素ee,返回值:0-失败;1-成功。
int PushBack(LinkList LL, ElemType* ee)
{
    //if ((LL == NULL) || (ee == NULL)) { printf("链表LL或元素ee不存在。n"); return 0; } // 判断表和元素是否存在。

    //LNode* pp = LL;  // 从头结点开始。

     找到最后一个结点。
    //while (pp->next != NULL) pp = pp->next;

    //LNode* tmp = (LNode*)malloc(sizeof(LNode));  // 分配一个结点。
    //if (tmp == NULL) return 0;  // 内存不足,返回失败。

     考虑数据元素为结构体的情况,这里采用了memcpy的方法而不是直接赋值。
    //memcpy(&tmp->data, ee, sizeof(ElemType));

     处理next指针。
    //tmp->next = NULL;
    //pp->next = tmp;

    //return 1;

    int len = LengthList(LL);
    return InsertList(LL, len+1, ee);

}

// 删除链表LL中第一个结点,返回值:0-位置不合法;1-成功。
int PopFront(LinkList LL)
{
    return DeleteNode(LL, 1);
}

// 删除链表LL中最后一个结点,返回值:0-位置不合法;1-成功。
int PopBack(LinkList LL)
{
    if (LL == NULL) { printf("链表LL不存在。n"); return 0; } // 判断表和元素是否存在。

    // 必须加上这个判断,否则下面的循环pp->next->next不成立。
    if (LL->next == NULL) { printf("链表LL为空,没有尾结点。n"); return 0; } // 判断表是否为空。

    // 要删除最后一个结点,必须找到最后一个结点的前一个结点。

    LNode* pp = LL;  // 从第0个结点开始。

    // 找到倒数第二个结点(包括头结点)。
    while (pp->next->next != NULL) pp = pp->next;

    // 释放最后一个结点。
    free(pp->next);
    pp->next = NULL;

    return 1;
}

// 打印链表中全部的元素。
void PrintList(LinkList LL)
{
    if (LL == NULL) { printf("链表LL不存在。n"); return; } // 判断链表是否存在。

    LNode* pp = LL->next;  // 从第1个结点开始。

    printf("当前链表的内容为:>");
    while (pp != NULL)
    {
        printf("%-3d", pp->data);  // 如果元素ee为结构体,这行代码要修改。
        pp = pp->next;
    }

    printf("n");

    
}

// 求链表的长度,返回值:>=0-表LL结点的个数。
int  LengthList(LinkList LL)
{
    if (LL == NULL) { printf("链表LL不存在。n"); return 0; } // 判断链表是否存在。

    LNode* pp = LL->next;  // 头结点不算,从第1个结点开始。

    int length = 0;

    while (pp != NULL) 
    { 
        pp = pp->next;
        length++;
    }

    return length;
}

// 判断链表是否为空,返回值:0-非空或失败,1-空。
int IsEmpty(LinkList LL)
{
    if (LL == NULL) return 0;

    if (LL->next == NULL) return 1;

    return 0;
}

// 获取链表中第ii个结点,成功返回结点的地址,失败返回空。
// 注意,ii可以取值为0,表示头结点。
LNode* LocateNode(LinkList LL, unsigned int ii)
{
    if (LL == NULL) { printf("链表LL不存在。n"); return NULL; } // 判断表和元素是否存在。

    LNode* pp = LL;  // 指针pp指向头结点,逐步往后移动,如果为空,表示后面没结点了。
    int kk = 0;      // kk指向的是第几个结点,从头结点0开始,pp每向后移动一次,kk就加1。

    while ((pp != NULL) && (kk < ii))
    {
        pp = pp->next; kk++;
    }

    if (pp == NULL) { printf("位置(%d)不合法,超过了表长。n", ii); return NULL; }

    return pp;
}

// 查找元素ee在链表LL中的结点地址,如果没找到返回NULL,否则返回结点的地址。
LNode* LocateElem(LinkList LL, ElemType* ee)
{
    LNode* pp = LL;  // 从第1个数据结点开始。

    while (pp != NULL)
    {
        // 如果数据元素是结构体,以下代码要修改。
        if (pp->data == *ee) return pp;

        pp = pp->next;
    }

    return NULL;
}

// 采用归并的方法,将两个升序的链表La和Lb,合并成一个升序的链表Lc。
int MergeList(LinkList La, LinkList Lb, LinkList Lc)
{
    if ((La == NULL) || (Lb == NULL) || (Lc == NULL)) { printf("表La、Lb、Lc至少有一个不存在。n"); return 0; }

    La = La->next;
    Lb = Lb->next;

    LNode* pp;

    // 把La和Lb合并到Lc中。
    while ((La != NULL) && (Lb != NULL))
    {
        // 取La和Lb的较小者。
        if (La->data <= Lb->data)
        {
            pp = La; La = La->next;
        }
        else
        {
            pp = Lb; Lb = Lb->next;
        }

        // 把较小者追加到Lc中。
        Lc->next = (LNode*)malloc(sizeof(LNode));  // 分配一个新结点。
        Lc = Lc->next;
        memcpy(&Lc->data, &pp->data, sizeof(ElemType));
        Lc->next = NULL;
    }

    // 把链表La其它的元素追加到Lc中。
    while (La != NULL)
    {
        Lc->next = (LNode*)malloc(sizeof(LNode));  // 分配一个新结点。
        Lc = Lc->next;
        memcpy(&Lc->data, &La->data, sizeof(ElemType));
        Lc->next = NULL;
        La = La->next;
    }

    // 把链表Lb其它的元素追加到Lc中。
    while (Lb != NULL)
    {
        Lc->next = (LNode*)malloc(sizeof(LNode));  // 分配一个新结点。
        Lc = Lc->next;
        memcpy(&Lc->data, &Lb->data, sizeof(ElemType));
        Lc->next = NULL;
        Lb = Lb->next;
    }

    return 1;
}

// 在指定结点pp之后插入元素ee,返回值:0-失败;1-成功。
int InsertNextNode(LNode* pp, ElemType* ee)
{
    if (pp == NULL) { printf("结点pp不存在。n"); return 0; }

    LNode* tmp = (LNode*)malloc(sizeof(LNode));
    if (tmp == NULL) return 0;

    memcpy(&tmp->data, ee, sizeof(ElemType));
    tmp->next = pp->next;
    pp->next = tmp;

    return 1;
}

// 在指定结点pp之前插入元素ee,返回值:0-失败;1-成功。
int InsertPriorNode(LNode* pp, ElemType* ee)
{
    if (pp == NULL) { printf("结点pp不存在。n"); return 0; }

    // 在指定结点pp之前插入采用偷梁换柱的方法:
    // 1、分配一个新的结点;
    // 2、把pp结点的数据和指针复制到新结点中。
    // 3、把待插入元素的数据存入pp结点中。

    LNode* tmp = (LNode*)malloc(sizeof(LNode));
    if (tmp == NULL) return 0;

    // 把pp结点的数据和指针复制到tmp中。
    memcpy(&tmp->data, &pp->data, sizeof(ElemType));
    tmp->next = pp->next;

    // 把待插入的元素存入pp中。
    memcpy(&pp->data, ee, sizeof(ElemType));
    pp->next = tmp;

    return 1;
}


//删除指定元素,返回值:0-失败;1-成功。
int DeleteElem(LinkList LL, ElemType* ee)
{
    if (LL == NULL) { printf("链表LL不存在。n"); return; } // 判断链表是否存在。

    // 要删除位序ii结点,必须找到ii-1结点。
    LNode* pp = LL->next;  // 指针pp指向头结点,逐步往后移动,如果为空,表示后面没结点了。
    int kk = 0;      // kk指向的是第几个结点,从头结点0开始,pp每向后移动一次,kk就加1。

    while ((pp != NULL) && (pp->next->data!=(*ee)))
    {
        pp = pp->next;
    }

    // 注意,以下行的代码与视频中的不一样,视频中的是 if ( pp==NULL ),有bug。
    if (pp->next == NULL) { printf("元素不存在,无法删除!n"); return 0; }

    LNode* tmp = pp->next;  // tmp为将要删除的结点。
    pp->next = tmp->next; 

    free(tmp);

    return 1;
}


// 把链表pp结点之后的结点原地逆置(反转),返回值:0-失败;1-成功。
void ReverseList(LNode* pp)
{
    LNode* ss;      // 当前结点。
    LNode* ssnext;  // 当前结点的下一结点。

    ss = pp->next;    // 从pp结点之后的结点开始反转。

    pp->next = NULL;  // pp->next指向空。

    while (ss != NULL)
    {
        ssnext = ss->next;  // 保留ss下一结点的地址。

        // 以下两行相当于在pp之后插入ss结点。
        ss->next = pp->next;
        pp->next = ss;

        ss = ssnext;  // ss结点后移。
    }
}

本课内容结束!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/949942.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号