赞
踩
链表是一种常见的重要的数据结构,是动态的进行内存分配的一种结构,通过结构体去进行建立。
链表中有一个“头指针”变量,本文中用 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; } // 这种方法的特点是能保持原链表的结构不发生改变 // 改变的只是链表中各个节点的数据: }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。