动态链表详解:

  • Post author:
  • Post category:其他




动态链表详解:



什么是链表:

链表是一种常见的重要的

数据结构

,是动态的进行

内存分配

的一种结构,通过

结构体

去进行建立。

链表中有一个“

头指针

”变量,本文中用

head

表示,它存放一个

地址

,这个地址指向链表的

第一个元素

。就这样一直连接下去,知道最后一个元素,它不再指向其他元素,称作是“

表尾

”,存放一个空地址 NULL 表示链表结束。

请添加图片描述

链表中的每个元素又叫“

结点

”。

每个节点都必须包含两部分:

  • 用户所需的实际数据
  • 下一个结点的地址。

我们建立链表的目的还是为了

存放数据

,因此节点中必须要有实际的数据存放,通过下一个结点的地址再去访问下一个结点,读取下一个结点的内容。

可以看成链表中的各元素在内存中的地址可以是

不连续的

,要找到某一元素,必须要找到它的上一个元素,根据它提供的地址,才能定位到下一个元素。因此链表的首地址很重要,相当于是整个链表的入口地址,通过它才能去访问整个地址。



链表是如何建立起来的:

显而易见,链表这种数据结构,必须通过

指针变量

才能实现,每个节点中必须包含指针变量,用它来存放下一个结点的地址。

而结构体变量,用它去建立链表是最合适的,因为一个结构体变量可以包含若干和不同的数据成员,用结构体变量去充当结点,可以存放很多的数据:

struct link {
    int num;
    char ch;
    char *str;
    float x;
    double y;
    struct link *next;
};

建立链表的结构体中可以包含很多数据类型和数据,与普通的结构体不同的是,该结构体必须包含一项为指向该结构体类型的指针 struct link *next 用来存放下一个节点的地址。

**注意:**上面的代码只是定义了一个叫 link 的结构体,实际上并没有分配内存空间,只有定义了结构体变量之后才会分配



静态链表:

静态链表使用的不多,因为静态链表中所有的数据都是在程序内部填充好的,不是临时开辟的,使用完后也不会释放。

#include <stdio.h>

// 创建结构体:
struct link {
    int num;
    char *name;
    struct link * next;
};

int main(void)
{
    // 定义结构体变量:
    struct link x, y, z, *head, *p;
    // 为结构体变量填充数据:
    x.num = 100; x.name = "wang";
    y.num = 110; y.name = "zhang";
    z.num = 120; z.name = "li";
    // 将结构体变量连接起来:
    head = &x;
    x.next = &y;
    y.next = &z;
    z.next = NULL;

    // 开始遍历链表:
    p = head;
    while(p != NULL)
    {
        printf("num:%d name:%s\n", p->num, p->name);
        p = p->next;
    }
    return 0;
}



动态链表:

建立动态链表是指在程序执行过程中从无到有的建立起一个链表,即一个个的开辟结点和输入各节点的数据,并建立起前后相连的关系。

这里用一个简单的结构体举例:

// 建立动态链表所用的结构体:
struct link {
    int num;
    struct link *next;
};

写了六个函数:

  • 开辟链表的函数
  • 输出链表的函数
  • 删除结点的函数
  • 插入结点的函数
  • 延长链表的函数
  • 链表排序的函数

其中,链表的开辟是最重要的一步,只要理解了动态链表开辟的过程,就能理解动态链表的基本组成原理。

下面是动态链表的函数与详解:

# include "stdio.h"
# include "stdlib.h"

# define LEN sizeof(struct link)

// 建立动态链表所用的结构体:
struct link {
    int num;
    struct link *next;
};

// 静态内存区的全局变量:负责记录节点个数:
static int n;

// 开辟链表的函数:
struct link *creat(void);

// 输出链表的函数:
void print(struct link *const);

// 删除动态链表节点的函数:x 为待删除的数据值:
struct link *del(struct link *, int);

// 插入节点的函数:
struct link *insert(struct link *, struct link *, int);

// 延长链表的函数:
struct link *extend(struct link *);

// 给链表排序的函数:
void sort(struct link *);

int main(void) {
    struct link *head;
    head = creat();
    print(head);
    int ch = '#';
    char *sentence = "请选择您的操作:\n0.退出程序  1.打印链表的值  2.删除链表节点  3.插入链表节点  4.延长链表  5.给链表排序:";
    while (1) {
        printf("%s", sentence);
        scanf("%d", &ch);
        if (ch == 0) {
            printf("程序退出!");
            break;
        }
        if (ch == 1) {
            print(head);
        }
        if (ch == 2) {
            if (head == NULL) {
                printf("链表为空!请先输入数值!\n");
            } else {
                int x;
                printf("请输入要删除的数值:");
                scanf("%d", &x);
                head = del(head, x);
                print(head);
            }
        }
        if (ch == 3) {
            int pos;
            struct link *plus = (struct link *) malloc(LEN);
            plus->next = NULL;
            if (head == NULL || n == 0) {
                printf("链表为空!请先添加数据!");
            } else {
                printf("请输入要插入的数值:");
                scanf("%d", &plus->num);
                printf("请输入要插入的位置:");
                scanf("%d", &pos);
                head = insert(head, plus, pos);
                print(head);
            }
        }
        if (ch == 4) {
            head = extend(head);
            printf("添加数据成功!\n");
            print(head);
        }
        if (ch == 5) {
            sort(head);
            printf("链表排序成功!\n");
            print(head);
        }
    }
}

// 开辟链表的函数:
struct link *creat() {
    // 定义三个结构体指针变量,其中head指向链表首地址:
    struct link *head = NULL, *p, *q;
    n = 0;
    // 使用 malloc 开辟第一个节点,然后使 p,q 指向那片空间:
    // malloc带回的是不指向任何类型的指针数据 void 类型
    // 需要通过(struct link *)强制类型转换
    p = q = (struct link *) malloc(LEN);
    // 输入数据,当输入0的时候停止建立:
    // 因为 p 指向这片空间,通过 p 把数据写入那片内存空间
    printf("请输入数据开始建立链表:输入0时停止:");
    scanf("%d", &p->num);
    while (p->num != 0) {
        // 让节点数目 +1;
        n++;
        if (n == 1) {
            // 当第一次建立节点时让 head 指向第一个节点:
            head = p;
        } else {
            // 将新开辟的节点的地址赋值给上一个节点的 next 成员
            // 因为 q 指向这一个节点,对 q 进行的操作就是对节点的操作
            // 通过这一步把新开辟的节点和上一个节点连接起来
            q->next = p;
        }
        // 第一次循环的时候没有作用,此时 p 和 q 都是指向第一个节点
        // 之后的循环使 q 也指向刚建立的那个节点:
        q = p;
        // 第一次循环的时候开辟第二个节点
        // 之后的循环也是开辟新的节点并让 p 指向那片空间
        p = (struct link *) malloc(LEN);
        // 通过 p1 再次把数据写入新的内存空间中:
        scanf("%d", &p->num);
    }
    // 最后让 q 指向空,使链表最后一个结点的 next 指向 NULL
    // 将 NULL 设置为填表结尾的标志:
    q->next = NULL;
    // 此时 p 正指向输入数据为0的地址空间,将其释放掉:
    free(p);
    printf("建立链表成功!\n");
    // 把指向表头的指针返回出去:
    return head;
}

// 输出链表的函数:
void print(struct link *const head) {
    // 这里将 head 指针类型传入为 const 保护 head 的地址不被改变
    struct link *p = head;
    // 确保链表不为空:
    if (head != NULL) {
        printf("链表中的数据为:\n");
        // 开始循环打印链表的数据,当到 NULL 时停止:
        do {
            printf("%d ", p->num);
            p = p->next;
        } while (p != NULL);
        printf("\n");
    } else {
        printf("链表为空!\n");
    }
}

// 删除动态链表节点的函数:有重复数据时默认删除第一个:
struct link *del(struct link *head, int x) {
    // 删除一个节点是把它从链表中分离出来,只要撤销原来的链接关系即可
    // 这里指定 x 的数据的值作为删除节点的标志
    struct link *p, *q;
    // 检测链表为空:
    if (head == NULL) {
        printf("链表为空!\n");
        return head;
    }
    p = head;
    // 通过循环条件寻找待删除的数据:
    while (x != p->num && p->next != NULL) {
        // 让 q 是始终是 p 前面的那个节点:
        // 通过循环让 p 定位到待删除的节点位置:
        // 这样可以将待删除节点前的节点(q位置)与待删除节点后的节点(p->next)位置连接起来
        q = p;
        p = p->next;
    }
    // 再次确认条件:开始删除节点,这里分为两种情况:
    if (x == p->num) {
        // 如果待删除的节点是第一个节点,就要将 head 后移一个位置:
        // 如果不是第一个节点,就断开原有的连接,重新建立连接:
        // 将待删除节点前的节点(q位置)与待删除节点后的节点(p->next)位置连接起来
        if (p == head) {
            head = p->next;
        } else {
            q->next = p->next;
        }
        printf("数值为%d的节点已删除!\n", x);
        // 将删除的那个节点的空间释放掉:
        free(p);
        // 节点数目减一;
        n--;
    } else {
        printf("未找到数值为%d的节点!\n", x);
    }
    // 返回新的头指针:
    return head;
}

// 插入节点的函数:
struct link *insert(struct link *head, struct link *data, int pos) {
    // 函数接受的参数是头指针、要插入的结构体指针和要插入的位置:
    struct link *t = data, *p = head, *q = p->next;
    int i;
    // 判断当链表为空时,将插入的数据作为第一个节点:
    // 当链表不为空时,将有三种情况:
    if (head == NULL || n == 0) {
        head = t;
        t->next = NULL;
    } else {
        // 当要插入的位置为链表的头部时,将插入节点的地址位置给 head
        // 然后再与后面的链表连接起来:
        if (pos == 1 || pos == 0) {
            head = t;
            t->next = p;
        } else if (pos > n) {
            // 当要插入位置位于链表的末尾时;先通过循环定位到链表的尾端
            // 将插入的节点与尾端相连,最后将新的尾端地址改为 NULL
            while (p->next != NULL) {
                p = p->next;
            }
            p->next = t;
            // 修改新的尾端:
            t->next = NULL;
        } else {
            // 当插入节点位于中间时,通过 p,q两个指针同时定位到插入位置前一个节点和后一个节点
            for (i = 2; i < pos && p->next != NULL; i++) {
                // 通过循环进行定位:
                p = p->next;
                q = q->next;
            }
            // 找到对应位置后插入节点:
            p->next = t;
            t->next = q;
        }
    }
    // 插入节点后让节点数目+1:
    n++;
    return head;
}

// 延长链表的函数:
struct link *extend(struct link *head) {
    // 延长链表指的是在链表的尾部继续添加数据:
    struct link *till = head, *p, *q;
    // 此时分为两种情况:
    // 第一种情况时当链表为空的时候,就要重新去开辟链表:
    // 第二种情况时当链表不为空的时候,就在链表的尾部添加数据:
    if (till == NULL) {
        printf("链表为空,请输入数据建立链表:输入0时停止:");
        p = q = (struct link *) malloc(LEN);
        // 链表为空时,要将新建立的空间的指针赋值给头指针 till
        till = p;
    } else {
        // 链表不为空时,先定位到链表的尾端:
        q = till;
        while (q->next != 0) {
            q = q->next;
        }
        printf("请输入您要添加的数据:输入0时停止:");
        // p 指针负责去开辟新的空间:
        p = (struct link *) malloc(LEN);
    }
    // 输入数据:
    scanf("%d", &p->num);
    // 通过循环去添加数据:
    // 此时不管之前链表是否为空,同样都是将数据添加到链表的尾部:
    while (p->num != 0) {
        // 只要有数据添加:节点数+1
        n++;
        // 将节点与后一个节点连接起来:
        q->next = p;
        // 跳转到末尾的那个节点上:
        q = p;
        p = (struct link *) malloc(LEN);
        scanf("%d", &p->num);
    }
    // 让最后一个节点指向空地址:
    q->next = NULL;
    // 释放 p 所占用的空间:
    free(p);
    return till;
}

// 给链表数据排序的函数:
void sort(struct link *head) {
    // 对链表进行排序其实并不是一件简单的事情,因为链表的各个数据并不是紧密连接的
    // 这里先用结构体数组来读取链表的数据,转化为对数组的排序:
    // 排序过后再将数据重新写入链表中,可以保持原链表的结构不发生改变:
    struct link a[n], *p = head, t;
    int i, j, swap;
    // 将链表中的数据写入结构体数组中数组中:
    for (i = 0; i < n; i++) {
        a[i] = *p;
        p = p->next;
    }
    // 通过冒泡排序对数组进行排序:
    for (i = 0; i < n; i++) {
        swap = 0;
        for (j = 0; j < n - i - 1; j++) {
            if (a[j].num > a[j + 1].num) {
                t = a[j];
                a[j] = a[j + 1];
                a[j + 1] = t;
                swap = 1;
            }
        }
        if (swap == 0) {
            break;
        }
    }
    // 将数据重新写入链表中:
    p = head;
    for (i = 0; i < n; i++) {
        p->num = a[i].num;
        p = p->next;
    }
    // 这种方法的特点是能保持原链表的结构不发生改变
    // 改变的只是链表中各个节点的数据:
}



版权声明:本文为wwx1239021388原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。