一、链表相关的定义
- 什么是链表:
链表是为了表示每个数据元素ai与其直接后继元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储本身的信息之外,还需要存储一个指示其直接后继的信息(即直接后继的存储位置)。我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息被称为指针或者链。这两部分信息组成数据元素ai的存储映像,称为节点(Node)。
n个节点(ai的存储映像)链接成一个链表,即为线性表(a1,a2,…,an)的链式存储结构,因为此链表的每个节点中只包含一个指针域,所以叫单链表。
- 链表的重要组成
头指针:链表中第一个结点或头结点的存储位置叫做头指针;它具有标识作用,常用头指针冠以链表的名字;无论链表是否为空,头指针均不为空,它是链表的必要元素;
头结点:单链表第一个结点前设置的一个结点叫做头结点(为了方便操作),其数据域一般无意义,也可以存放链表的长度;其不一定是链表的必要元素;
二、链表的代码描述
1.节点结构定义
typedef struct LinkList {
ElemType data; //数据域,ElemType指代数据类型
struct LinkList* next; //指针域
}Node;
2.链表的读取
思路:声明一个指针p,使其指向链表的第一个节点,然后while循环使得p一直往下一个节点走,直到i == index时或者p为空时,退出循环;此时判断p是否为空,如果为空说明p已经走完了整个链表,那么说明该链表不存在index所指节点,index超出了链表的范围或者链表的节点数;否则的话,说明p不为空并且i == index,那么说明成功找到了该节点,将p->data返回给*data;
参数:
Node &list [in] 已存在链表的引用
int index [in] 需要读取的数据位置索引
int *data [inout] 返回读取的值
void GetData(Node &list, int index, int *data) {
Node* p;
int i = 1;
p = list.next;
while(p && i < index) {
p = p->next;
++i;
}
if(!p || i > index) {
cout << "不存在该节点!" << endl;
return;
}
*data = p->data;
cout << "链表中第" << index << "个元素的值为:" << *data << endl;
}
3.链表的插入和删除
思路:插入的标准语句:newNode->next = p->next; p->next = newNode;p为你想插入的位置的前一个位置,你所需要的就是讲p与它后面一个节点的链接断开,所以你需要让插入的新节点先与后一个节点链接上,然后在让p把新的接点连接上,这样就算插入成功了;至于怎么找到这个位置,和读取时候一样遍历链表;
参数:
Node &list [in] 已存在链表的引用
int index [in] 需要插入的数据位置索引
int *data [in] 需插入的数据
void ListInsert(Node& list, int index, int data) {
int i = 1;
Node* newNode,*p;
p = &list;
while(p && i < index) {
p = p->next;
++i;
}
if(!p || i > index) {
cout << "不存在第" << index << "个位置!" <<endl;
return;
}
newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = p->next;
p->next = newNode;
cout << "成功在链表中第" << index << "个位置插入:" << data << endl;
}
思路:删除节点的标准语句,p->next = delNode->next;free(delNode); 先让p->next接手delNode的下一个节点,然后再将delNode的数据按需要返回,再将其申请的内存释放;如何找到该节点也与读取操作遍历链表方式一样;
参数:
Node &list [in] 已存在链表的引用
int index [in] 需要删除的数据位置索引
int *data [inout] 返回需删除的数据
void ListDelete(Node& list, int index, int *data) {
Node* p,*delNode;
p = &list;
int i = 1;
while(p && i < index) {
p = p->next;
++i;
}
if(!p || i > index) {
cout << "不存在第" << index << "个位置!" <<endl;
return;
}
delNode = p->next;
*data = delNode->data;
p->next = delNode->next;
delNode->next = NULL;
cout << "成功删除链表中第" << index << "个元素:" << *data << endl;
}
4.链表的整表创建
思路:用头插法方式构建链表,就是将构建的新节点依次插入头结点之后,newNode->next = list.next;list.next = newNode;
srand((unsigned)time(NULL));初始化随机数种子;
data = rand() % 100;data取100以内的随机数;
参数:
Node &list [in] 已存在链表的引用
int data [in] 新创建节点的数据
void InsertByHead(Node& list, int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = list.next;
list.next = newNode;
}
参数:
Node &list [in] 已存在链表的引用
int len [in] 创建链表的长度
void CreateList(Node& list, int len) {
int data;
srand((unsigned)time(NULL));
for(int i = 0; i < len; ++i) {
data = rand() % 100;
InsertByHead(list, data);
}
cout << "成功创建长度为 " << len << " 的链表!" << endl;
}
5.链表的整表删除
思路:循环遍历链表,只要当遍历到的节点不为空时,就销毁该节点,直到只剩一个头结点,将其指针域置空;
参数:
Node &list [in] 欲删除的链表
void ClearList(Node& list) {
Node* p,*temp;
p = list.next;
while(p) {
temp = p->next;
free(p);
p = temp;
}
list.next = NULL;
cout << "成功销毁链表!" << endl;
}
三、测试代码
#include<iostream>
#include<cstdlib>
#include<ctime>
using namespace std;
int main() {
Node list;
list.next = NULL;
CreateList(list, 10); //创建长度为10的链表
int data;
GetData(list, 5, &data); //取第五个节点的数据
ListInsert(list, 6, 1234); //在第六个位置插入1234
ListDelete(list, 9, &data); //删除第九个节点并返回其数据
//遍历链表
Node* p = list.next;
while(p) {
cout << p->data << endl;
p = p->next;
}
ClearList(list); //销毁链表
getchar();
return 0;
}
四、总结
链表确实是数据结构比较重要也比较基础的部分,而且链表还有循环链表、静态链表、双向链表几种。学好链表的目的是为了更好的掌握链式存储结构,更好的理解指针,理解数据在内存中是什么样的存在,各种数据之间是怎么关联起来的。这对后面学习像树啊图啊这样比较复杂比较丰富的数据结构时,会很有帮助。
总结一些经验,对比链表这种链式存储结构和数组等一些顺序存储结构,其优势在于插入和删除比较方便,不影响其他的节点数据,但是也因为他不是一种连续的存储形式,在查找时需要遍历,所以选择使用哪种数据结构也要根据实际需要来权衡选择。没有最优的数据结构,只有最合适的数据结构。