链表是线性表的一种,是一种物理存储单元上非连续的存储结构,链表中的数据元素之间是通过指针链接实现的。
链表由一系列节点组成,节点可以在运行时动态的生成。
链表中国的每个节点分为两部分:一部分是存储数据的数据域,另一部分是存储下一个节点的地址的指针域。
如果要在链表中查找某个位置的元素,需要从第一个元素开始,循着指针链一个节点一个节点的找,不像顺序表那样可以直接通过下标获取对应的元素,因此,链表不适合查询操作频繁的场景。
如果要在链表中添加或删除某个元素,只需要通过指针操作,将要操作的节点链入指针链或从指针链中移除即可,不必像顺序表那样需要移动之后的所有节点,因此,链表更适合增删操作频繁的场景。
使用链表不需要像顺序表那样,处处考虑要不要给表扩容,链表中的元素都是在运行时动态生成的,因此可以充分利用计算机的内存空间;但是,由于链表中的每个元素都需要包括数据域和指针域两块区域,因此空间开销也是比较大的。
下面是用 C语言 描述的链表的代码:
链表数据结构的头文件LinkedList.h中的代码:
/** * 线性表(链式存储) * 注意:线性表的第一个节点不存储任何数据,只起到表头的作用 */ #include <stdio.h> #include <stdlib.h> // 类型定义 typedef int Status; // 方法的返回值 typedef int LinkedElemType; // LinkedList数据结构中节点中存储的数据的类型 // LinkedList中的节点的结构体 typedef struct LinkedNode { LinkedElemType value; struct LinkedNode* nextNode; } LinkedNode; // LinkedList数据结构 typedef struct LinkedList { LinkedNode* data; int length; } LinkedList; // 1.创建带头结点的空链表 void initLinkedList(LinkedList* L) { L->data = (LinkedNode*)malloc(sizeof(LinkedNode)); if(L->data != NULL) { L->data->nextNode = NULL; L->length = 0; printf("创建链表成功!\n"); } } // 2.销毁链表 void destroyLinkedList(LinkedList* L) { LinkedNode* node = NULL; if(L->data == NULL) { printf("链表不存在!\n"); exit(1); } while(L->data != NULL) { node = L->data; L->data = L->data->nextNode; free(node); } printf("销毁链表成功!\n"); } // 3.清空链表(使链表只剩下表头) void clearLinkedList(LinkedList* L) { LinkedNode* node = NULL; if(L->data == NULL) { printf("链表不存在!\n"); exit(1); } while(L->data->nextNode != NULL) { node = L->data->nextNode; L->data->nextNode = node->nextNode; free(node); } L->length = 0; printf("清空链表成功!\n"); } // 4.返回链表的长度 int getLinkedListSize(LinkedList* L) { return L->length; } // 5.判断链表中是否存储着数据 Status isLinkedListEmpty(LinkedList* L) { return L->data->nextNode == NULL; } // 6.返回链表中第i个数据元素的值 LinkedElemType getLinkedElemAtPos(LinkedList* L, int i) { LinkedNode* node = NULL; int index = -1; if(L->data == NULL) { printf("链表不存在!\n"); exit(1); } if(i < 1 || i > L->length) { printf("下标无效!\n"); exit(1); } node = L->data; for(index = 1; index <= i; index++) { node = node->nextNode; } return node->value; } // 7.在链表L中检索值为e的数据元素(第一个元素) LinkedNode* getLinkedElem(LinkedList* L, LinkedElemType e) { LinkedNode* node = L->data; if(L->data == NULL) { printf("链表不存在!\n"); return NULL; } while(node->nextNode != NULL) { node = node->nextNode; if(e == node->value) { return node; } } return NULL; } // 8.在链表L中第i个数据元素之前插入数据元素e void insertLinkedElemBefore(LinkedList* L, int i, LinkedElemType e) { LinkedNode* priorNode = L->data; LinkedNode* nextNode = NULL; LinkedNode* newNode = NULL; int index = -1; if(L->data == NULL) { printf("链表不存在!\n"); exit(1); } if(i < 1 || i >= L->length) { printf("下标无效!\n"); exit(1); } newNode = (LinkedNode*)malloc(sizeof(LinkedNode)); newNode->value = e; for(index = 1; index < i; index++) { priorNode = priorNode->nextNode; nextNode = priorNode->nextNode; } priorNode->nextNode = newNode; newNode->nextNode = nextNode; L->length++; printf("在第%d个位置插入%d成功!\n", i, e); } // 9.在表尾添加元素e void insertElemAtEnd(LinkedList* L, LinkedElemType e) { LinkedNode* currNode = NULL; LinkedNode* newNode = NULL; if(L->data == NULL) { printf("链表不存在!"); exit(1); } currNode = L->data; while(currNode->nextNode != NULL) { currNode = currNode->nextNode; } newNode = (LinkedNode*)malloc(sizeof(LinkedNode)); newNode->value = e; newNode->nextNode = NULL; currNode->nextNode = newNode; L->length++; printf("成功在表尾添加元素%d\n", e); } // 10.删除链表中第i个位置上的元素 void deleteLinkedElemAtPos(LinkedList* L, int i) { LinkedNode* priorNode = L->data; LinkedNode* nextNode = NULL; LinkedNode* oldNode = NULL; int index = -1; if(L->data == NULL) { printf("链表不存在!\n"); exit(1); } if(i < 1 || i > L->length) { printf("下标无效!\n"); exit(1); } for(index = 1; index < i; index++) { priorNode = priorNode->nextNode; nextNode = priorNode->nextNode; } oldNode = nextNode; priorNode->nextNode = nextNode->nextNode; L->length--; printf("成功删除第%d个位置上的元素%d!\n", i, oldNode->value); free(oldNode); } // 11.返回给定元素的前驱节点 LinkedNode* getPriorLinkedElem(LinkedList* L, LinkedNode* e) { LinkedNode* priorNode = NULL; LinkedNode* currNode = NULL; if(L->data == NULL) { printf("链表不存在!"); return NULL; } if(e == L->data->nextNode) { return L->data->nextNode; } priorNode = L->data; currNode = priorNode->nextNode; while(currNode->nextNode != NULL) { if(currNode == e) { return priorNode; } priorNode = currNode; currNode = priorNode->nextNode; } return NULL; } // 12.返回给定元素的后继节点 LinkedNode* getNextLinkedElem(LinkedList* L, LinkedNode* e) { LinkedNode* currNode = NULL; if(L->data == NULL) { printf("链表不存在!\n"); return NULL; } currNode = L->data; while(currNode->nextNode != NULL) { if(currNode == e) { return currNode->nextNode; } currNode = currNode->nextNode; } return NULL; } // 13.遍历链表 void traverseLinkedList(LinkedList* L) { LinkedNode* currNode = NULL; if(L->data == NULL) { printf("链表不存在!\n"); exit(1); } currNode = L->data->nextNode; while(currNode != NULL) { printf("%-4d", currNode->value); currNode = currNode->nextNode; } printf("\n"); } testLinkedList() { // 声明链表对象 LinkedList list; // 测试节点 LinkedNode* testNode; // 初始化链表 initLinkedList(&list); // 销毁链表 // destroyLinkedList(&list); // 清空链表 clearLinkedList(&list); // 获取链表长度 printf("当前链表长度:%d\n", getLinkedListSize(&list)); // 判断链表中是否存储着数据 printf("链表中是否存储着数据:%s\n", isLinkedListEmpty(&list) ? "否" : "是"); // 在表尾添加元素 insertElemAtEnd(&list, 1); insertElemAtEnd(&list, 2); insertElemAtEnd(&list, 4); insertElemAtEnd(&list, 5); insertElemAtEnd(&list, 6); // 遍历链表中的元素 traverseLinkedList(&list); // 获取某个位置的元素值 printf("当前链表中第2个元素的值是:%d\n", getLinkedElemAtPos(&list, 2)); // 在某元素前插入新元素 insertLinkedElemBefore(&list, 3, 3); insertLinkedElemBefore(&list, 3, 3); // 遍历链表中的元素 traverseLinkedList(&list); // 删除某位置的元素 deleteLinkedElemAtPos(&list, 4); // 遍历链表中的元素 traverseLinkedList(&list); // 获取对应值的第一个元素 testNode = getLinkedElem(&list, 3); // 返回某节点的前驱节点 printf("测试节点的前驱节点的值是:%d\n", getPriorLinkedElem(&list, testNode)->value); // 返回某节点的后继节点 printf("测试节点的后继节点的值是:%d\n", getNextLinkedElem(&list, testNode)->value); }
主函数所在的文件main.c中的代码:
#include <LinkedList.h> //主函数 int main() { testLinkedList(); // 线性表(链式存储)结构的测试 return 0; }
运行结果如下:
创建链表成功!
清空链表成功!
当前链表长度:0
链表中是否存储着数据:否
成功在表尾添加元素1
成功在表尾添加元素2
成功在表尾添加元素4
成功在表尾添加元素5
成功在表尾添加元素6
1 2 4 5 6
当前链表中第2个元素的值是:2
在第3个位置插入3成功!
在第3个位置插入3成功!
1 2 3 3 4 5 6
成功删除第4个位置上的元素3!
1 2 3 4 5 6
测试节点的前驱节点的值是:2
测试节点的后继节点的值是:4
Process returned 0 (0x0) execution time : 0.031 s
Press any key to continue.
链表分为好几种,上面介绍的这种链表叫做线性链表,它的特点是:线性存储,只能通过前一个节点找到后一个节点,不能通过后一个节点找到前一个节点;
链表还有其他的几种,下面来简单介绍:
1、 循环链表:
链表的头尾节点相连,形成一个环。
实现方式:我们只需要将线性链表稍加改造,将尾节点的下一个节点的指针指向头结点即可。
2、双向链表:
既可以通过前一个节点找到后一个节点,也可以通过后一个节点找到前一个节点。
实现方式:在线性链表的节点数据结构体中,不但要定义指向一个节点的指针,也要定义指向前一个节点的指针。