IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 链表的设计 -> 正文阅读

[数据结构与算法]链表的设计

定义

????????链表是一种常见的基础数据结构,利用结构体指针来实现一个链表。链表可以动态的进行存储分配,也就是说,链表可以理解为是一个功能极为强大的数组,他可以在节点中定义多种数据类型,还可以根据需要随意增添,删除,插入节点。

????????链表都有一个头指针,一般以head来表示,存放的是一个地址。链表的每一个元素叫做节点,每个节点包含数据域和指针域;链表中的节点分为两类,头结点和一般节点,头结点是没有数据域的。链表中每个节点都分为两部分,一个数据域,一个是指针域。总而言之,在链表中head指向第一个元素:第一个元素又指向第二个元素;直到最后一个元素,该元素的指针域不再指向其它元素,它的指针域部分放一个“NULL”(表示“空地址”),链表到此结束。总而言之,链表的头结点没有前驱,有一个后继,末尾节点没有后继,只有唯一的前驱,其余每一个节点只有唯一的一个前驱和后继。对于循环链表,即是将其尾结点的指针域指向头结点,这样就构成了一个闭环,即为单向循环链表。

?

? ? ? ?对链表的操作有许多,比如:链表的创建,修改,删除,插入,输出,排序,反序,清空链表的元素,求链表的长度等等。

对于链表,常见的一般是单向链表,其定义方式如下:

  1. typedef struct student{

  2. int ID;

  3. struct student *next;

  4. } LinkList;

?此处定义了一个学生结构体,并将其重命名为LinkList,对于每一个学生节点,其内部包含了他的ID信息和下一个学生节点的地址,存储这样一系列的节点的结构就被称之为链表。

实例

下面以一个实例对单向链表进行讲解?:

#ifndef _LINKLIST_H
#define _LINKLIST_H

typedef int linklist_data_t;

typedef struct linklist{
    linklist_data_t data;
    struct linklist* next;
}lkl_node, *lkl_pnode;

//创建头节点
lkl_pnode create_linklist();

//插入
int insert_linklist(lkl_pnode H, int pos, linklist_data_t data);

//打印
int show_linklist(lkl_pnode H);

//判空
int empty_linklist(lkl_pnode H);

//获取链表长度
int getlen_linklist(lkl_pnode H);

//删除
int delete_linklist(lkl_pnode H, linklist_data_t data); //按值删除

int delete1_linklist(lkl_pnode H, int pos);//按位置删除
//查询
linklist_data_t search_linklist(lkl_pnode H, int pos); //按位置查询值
int search1_linklist(lkl_pnode H, linklist_data_t data);//按值查位置
//修改
int change_linklist(lkl_pnode H, int pos, linklist_data_t data);  //按位置修改值
int change1_linklist(lkl_pnode H, linklist_data_t old_data, linklist_data_t new_data);
//清空
int clean_linklist(lkl_pnode H);
//销毁
int destroy_linklist(lkl_pnode *H);
//链表的逆序
int  linklist_reverse(lkl_pnode H);
//链表的排序
int linklist_sort(lkl_pnode H);

#endif

?????????在上述头文件中,我们定义了一个存储int型数据的节点结构体?,使用typedef int linklist_data_t;的原因是如果直接在结构体内部声明data的数据类型为int,若后期需要存储其他类型的数据如char、double、float等,就需要去结构体内部更改,代码的复用性就大大降低了。而使用上面这种方式,只需要将typedef int linklist_data_t;改为typedef char?linklist_data_t;就可以实现从存储int型转换为存储char型或则其他类型。

????????同时,在头文件中我们声明了对链表实现某些操作的功能函数,比如:链表的创建,修改,删除,插入,输出,排序,反序,清空链表的元素,求链表的长度等等。

接下来是这些功能函数的实现方法:

1、链表的创建

//创建头节点
lkl_pnode create_linklist()
{
    //创建堆区空间
    lkl_pnode H = (lkl_pnode)malloc(sizeof(lkl_node));
    if (NULL == H)
    {
        printf("malloc is default\n");
        return NULL;
    }
    H->next = NULL;  //一开始是没有其他节点的,所以头节点指向NULL
    H->data = -1;//对于头结点的数据域,不使用,初始化为-1,以便用户识别
    return H;
}

对于链表的创建,只需要创建一个头结点即可,后续的其他操作均是在头结点后面进行。对于创建函数,返回链表的头结点。

2、链表的判空

//判空
int empty_linklist(lkl_pnode H)
{
    if (H->next == NULL)
        return 0;
    else
        return -1;
}

?因为头结点的指针域存储的是下一个节点的地址,故若头结点指针域为空,则代表该链表为空。

3、获取链表对的长度

int getlen_linklist(lkl_pnode H)
{
    int count = 0;
    while (H->next)
    {
        H = H->next;
        count++;
    }

    return count;
}

?在获取链表的长度时需要注意的是,头结点并不算做是有效节点;故应该从H->next即第一个节点开始计数。

4、插入

//插入
int insert_linklist(lkl_pnode H, int pos, linklist_data_t data)
{
    //判断插入位置是否合法
    if (pos < 0 || pos > getlen_linklist(H))
    {
        printf("pos is default\n");
        return -1;
    }

    //找到插入位置的前一个节点的位置
    while (pos--)
    {
        H = H->next;
    }
    //插入,头插
    lkl_pnode new = create_linklist();
    new->data = data;

    new->next = H->next;
    H->next = new;

    return 0;
}

此处实现的是按位置插入,核心点是使用while循环找到要插入的位置的前驱节点,然后使用头插的方式将该节点插入。

5、打印?

//打印
int show_linklist(lkl_pnode H)
{
    if (0 == empty_linklist(H))
    {
        printf("H is empty\n");
        return -1;
    }

    //遍历
    while (H->next)
    {
        H = H->next;
        printf("dat=%d ", H->data);
    }
    printf("\n");
}

打印的思想比较简单,即遍历一遍链表,挨个打印即可

6、删除?

删除有两种方式,即按位置删除和安置删除

首先是按值删除

int delete_linklist(lkl_pnode H, linklist_data_t data) //按值删除
{
    if (0 == empty_linklist(H))
    {
        printf("H is empty\n");
        return -1;
    }
    
    lkl_pnode p = H;
    lkl_pnode q = H->next;
    while (q)
    {
        if (q->data == data)
        {
            p->next = q->next;
            free(q);
            q = p->next;
            continue;
        }
        p = q;
        q = q->next;
    }
}

安值删除需要借助两个辅助指针一前一后,若后一个指针的数据域与要删除的值相等,则前一个指针的指针域直接指向后一个指针的指针域(?p->next = q->next;);即断开了要删除的节点,然后释放掉该节点即完成删除。

按位置删除

int delete1_linklist(lkl_pnode H, int pos)//按位置删除
{
    if (0 == empty_linklist(H))
    {
        printf("H is empty\n");
        return -1;
    }

    if (pos < 0 || pos >= getlen_linklist(H))
    {
        printf("pos is default\n");
        return -1;
    }
    //找到要删除的前一个节点位置
    while (pos--)
    {
        H = H->next;
    }
    //保存要删除的节点的位置
    lkl_pnode p = H->next;
    //连线
    H->next = p->next;
    //释放空间
    free(p);
    p = NULL;

    return 0;
}

按位置删除比较简单,找到该位置的前驱节点,断开连接,释放空间即可。

7、查询?

同上,查询也分为按值查询和按位置查询,其实现代码如下:

//查询
linklist_data_t search_linklist(lkl_pnode H, int pos) //按位置查询值
{

    if (0 == empty_linklist(H))
    {
        printf("H is empty\n");
        return -1;
    }

    if (pos < 0 || pos >= getlen_linklist(H))
    {
        printf("pos is default\n");
        return -1;
    }

    while (pos--)
    {
        H = H->next;
    }
    return H->next->data;
}
int  search1_linklist(lkl_pnode H, linklist_data_t data)//按值查位置
{
    if (0 == empty_linklist(H))
    {
        printf("H is empty\n");
        return -1;
    }
    int pos=0;
    H = H->next;
    while (H)
    {
        if (H->data == data) 
            break;    
        pos++;
        H = H->next;
    }
    return pos;
}

?8、修改

修改也有两种方式,按位置修改,安置修改;

//修改
int change_linklist(lkl_pnode H, int pos, linklist_data_t data) //按位置修改值
{
    if (0 == empty_linklist(H))
    {
        printf("H is empty\n");
        return -1;
    }

    if (pos < 0 || pos >= getlen_linklist(H))
    {
        printf("pos is default\n");
        return -1;
    }

    while (pos--)
    {
        H = H->next;
    }
    H->next->data = data;

    return 0;
}

int change1_linklist(lkl_pnode H, linklist_data_t old_data, linklist_data_t new_data)//按值修改
{
    if (0 == empty_linklist(H))
    {
        printf("H is empty\n");
        return -1;
    }
    H = H->next;
    while (H)
    {
        if (H->data == old_data)
            H->data = new_data;
        H = H->next;
    }

}

?9、清空

//清空
int clean_linklist(lkl_pnode H)
{
    if (0 == empty_linklist(H))
    {
        printf("H is empty\n");
        return -1;
    }

    while (empty_linklist(H))
    {
        delete1_linklist(H, 0);
    }

    return 0;
}

?这里需要注意两点:一是对链表的清空,即释放除头结点以外的所有节点,故可以调用删除函数来挨个删除元素即可;二是链表清空后,其头结点还在,热然可以对齐进行插入等操作;

10、销毁

//销毁                                            
int destroy_linklist(lkl_pnode* H)
{
    if (0 == empty_linklist(*H))
    {
        free(*H);
        *H = NULL;
    }
    else
    {
        clean_linklist(*H);
        free(*H);
        *H = NULL;
    }

    return 0;
}

对于链表的销毁,也有两点需要注意,首先是链表的销毁即释放掉链表的所有节点,包括头结点,故链表销毁后不可以再使用;其次是,对于链表的销毁,该函数需要进行地址传递,才能通过形参来改变实参,即销毁掉实参链表,否则只是对链表进行了清空,链表头结点实际还存在,要想完全释放掉链表,主函数中还需要再free一次并置空。

11、链表的逆序?

链表的逆序主要思想为将头结点与后面的节点断开,分成两个链表,然后一一取出后面节点组成的链表节点对头结点链表做头插,即可完成链表的逆序。

//链表的逆序
int  linklist_reverse(lkl_pnode H)
{
    if (0 == empty_linklist(H))
    {
        printf("H is empty\n");
        return -1;
    }
    lkl_pnode  p, q;
    p = q = H->next;
    H->next = NULL;
    while (q != NULL)
    {
        q = q->next;
        insert_linklist(H, 0, p->data);
        p = q;
       
    }

}

12、链表的排序

对链表进行排序的思想与逆序相似,首先也是断开头结点分成两个链表,然后一一取出后面链表的节点与头结点链表的每一个节点进行比较后插入,循环往复实现排序。

本例实现的是从小到大的排序:

//链表的排序
int linklist_sort(lkl_pnode H)
{
    if (0 == empty_linklist(H))
    {
        printf("H is empty\n");
        return -1;
    }
    lkl_pnode p, q,t;
    q = H->next;
    H->next = NULL;
    while (q != NULL)
    {
        p = q;
        q = q->next;
        t = H;
        while (t)
        {
            if (t->next == NULL)
            {
                p->next = t->next;
                t->next = p;
                break;
            }
            if (t->next->data >= p->data)
            {
                p->next = t->next;
                t->next = p;
                break;
            }
            else
            {
                t = t->next;
            }
        }
    }
}

总结?

以上便是对链表所有操作函数的实现,下面看一下实现效果:

int main()
{
    lkl_pnode H = create_linklist();

    puts("-------------------insert 0-9--------------");
    int i;
    for (i = 0; i < 10; i++)
    {
        insert_linklist(H, 0, i);
    }
    show_linklist(H);
    puts("\n");
    puts("-----------------delete pos=2--------------");
    delete1_linklist(H, 2);
    show_linklist(H);
    puts("\n");
    
    puts("-----------------change pos=2  data=666-----");
    change_linklist(H, 2, 666);
    show_linklist(H);
    puts("\n");
    puts("-----------------change pos=3  data=666-----");
    change_linklist(H, 3, 666);
    show_linklist(H);
    puts("\n");
    puts("-----------------change old=0  new=777-----");
    change1_linklist(H, 0, 777);
    show_linklist(H);
    puts("\n");
    puts("----------------- 排序----------------------");
    linklist_sort(H);
    show_linklist(H);
    puts("\n");
    puts("-----------------delete val=777--------------");
    delete_linklist(H, 777);
    show_linklist(H);
    puts("\n");
    puts("-----------------search pos=4 ---------------");
    printf("data=%d\n", search_linklist(H, 4));
    puts("\n");
    puts("-----------------search val=3 ---------------");
    printf("pos=%d\n", search1_linklist(H, 3));
    puts("\n");
    puts("-----------------change pos=2  data=888-----");
    change_linklist(H, 2, 888);
    show_linklist(H);
    puts("\n");
    puts("-----------------change pos=3  data=999-----");
    change_linklist(H, 3, 999);
    show_linklist(H);
    puts("\n");
    puts("-----------------逆序-----------------------");
    linklist_reverse(H);
    show_linklist(H);
    puts("\n");
    puts("----------------- 排序----------------------");
    linklist_sort(H);
    show_linklist(H);
    puts("\n");
    puts("-----------------clean-----------------------");
    clean_linklist(H);
    show_linklist(H);
    puts("-----------------destroy---------------------");
    destroy_linklist(&H);
    printf("H=%p\n", H);
}

上面是主函数的测试程序,包含了所有设计函数的使用,以下是输出结果:

?

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-09-13 11:43:26  更:2022-09-13 11:48:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 14:55:46-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码