最近回顾了一下数据结构,动手重新写了一遍单链表,实现的功能包括增加,删除,修改,查找,排序和反转。整理了下实现思路和代码想在这里分享一下,如有建议或错误的地方欢迎指出!
概述
n个结点可以由指针连接成一个链表,它是线性表的链式存储映像,称为线性表的链式存储结构。其中,结点只有一个指针的链表,称为单链表。
单链表中的几个重要概念
- 结点:数据元素的储存映象,包括数据域和指针域。
- 头指针:指向链表中第一个结点的指针。
- 首元结点:链表中存储第一个数据元素的结点。
- 头结点:在链表首元结点之前附设的一个结点,其数据域为空。
注:头结点的存在不是必须的。
功能描述
函数名 | 用途 |
---|---|
void prepend(int data) | 在头部添加元素 |
void append(int data) | 在尾部添加元素 |
void insertByIndex( int index, int data) | 通过指定的索引添加元素 |
void deleteByValue(int data) | 根据数据值来删除数据 |
void deleteByIndex(int index) | 根据索引来删除数据 |
void clearList() | 删除链表中的所有数据 |
void changeValue(int index, int data) | 根据索引更改数据 |
int getValue(int index) | 通过索引查询对应的数值 |
int getIndex(int data) | 通过数值查询对应的索引 |
void reverse() | 反转链表 |
void sort(bool ascending = true) | 给链表进行升序排序或者降序排序 |
void display() | 打印链表元素 |
代码实现
增加元素
增加元素主要分为前插,后插和插入这三种情况。
前插 - prepend()函数
这个函数可以在链表头部添加元素。
实现步骤:
- 新建一个结点new_node,将传入的数据放置在new_node的数据域中。
- 将首元结点的地址值传给new_node的指针域。
- 将new_node设为新的首元节点。
- 链表长度加一。
代码段:
void LinkedList::prepend(int data)
{
Node* new_node = new Node();
new_node->data = data;
new_node->next = head;
head = new_node;
length++;
}
后插 - append()函数
这个函数可以在链表尾部添加元素。
实现步骤:
-
新建一个结点new_node,将传入的数据放置在这个结点的数据域中,并将new_node的指针域设为空(nullptr)。
-
判断首元结点是否为空:
如果为空:将new_node设为新的首元节点。
如果不为空:定义一个指针p,将首元结点的地址值传递给p。然后通过while循环,直到找到指针p的指针域为空的结点,这个结点就是原链表的尾结点。最后把new_node(地址)放置在尾结点的指针域中。 -
链表长度加一。
代码段:
void LinkedList::append(int data) {
Node* new_node = new Node();
new_node->data = data;
new_node->next = nullptr;
if (head == nullptr) {
head = new_node;
}
else {
Node* p = head;
while (p->next != nullptr) {
p = p->next;
}
p->next = new_node;
}
length++;
}
插入 - insertByIndex()函数
这个函数可以实现在指定索引的结点前插入元素。
实现思路:
-
进行三个判断:
如果索引值大于链表长度,则不会插入结点。
如果索引值小于等于0,则调用prepend()函数前插结点。
如果索引值等于链表长度,则调用append()函数后插结点。 -
定义一个整型变量currentIndex表示当前索引的位置,初始化为0.
-
定义一个指针p,将首元结点的地址值传递给p。
-
使用while循环找到对应索引对应的指针位置。
-
创建一个新结点new_node,将数据传入到new_node的数据域中。再把此时p结点的指针域传递给new_node的指针域,然后把new_node结点的地址值放入到p结点的指针域中。
-
链表长度加一。
代码段:
void LinkedList::insertByIndex(int index, int data) {
if (index > length) {
return;
}
if (index <= 0) {
prepend(data);
return;
}
if (index == length) {
append(data);
return;
}
int currentIndex = 0;
Node* p = head;
while (p->next != nullptr && currentIndex < index - 1) {
p = p->next;
currentIndex++;
}
Node* new_node = new Node();
new_node->data = data;
new_node->next = p->next;
p->next = new_node;
length++;
}
测试案例输出结果:
删除元素
删除元素主要分为根据数据删除元素,根据索引删除元素和删除所有元素(清空链表)三种情况。
deleteByValue()
这个函数可以实现根据给定的数据值来删除结点元素。
实现思路:
- 先进行两个判断:
如果首元结点为空,则不进行删除。
如果首元结点的数据域等于给定的数据值,则直接删除首元结点,并将链表长度减一。 - 定义指针p,将首元结点的地址值传递给p。
- 定义指针q,并设为空(nullptr)。
- 进行while循环,假设要删除的结点是ai,当p指向ai-1时,让q指向结点ai,然后把q的指针域的值传给p的指针域,再删除q指向的结点。最后把链表长度减一。
代码段:
void LinkedList::deleteByValue(int data) {
if (head == nullptr) {
return;
}
if (head->data == data) {
Node* h = head;
head = head->next;
delete h;
length--;
return;
}
Node* p = head;
Node* q = nullptr;
while (p != nullptr) {
if (p->next == nullptr) {
return;
}
if (p->next->data == data) {
q = p->next;
p->next = q->next;
delete q;
length--;
return;
}
p = p->next;
}
}
deleteByIndex()
这个函数可以实现根据给定的索引来删除结点元素。
实现思路可参考上面的deleteByValue(),两者类似。
代码段:
void LinkedList::deleteByIndex(int index) {
if (head == nullptr) {
return;
}
if (index == 0) {
Node* h = head;
head = head->next;
delete h;
length--;
return;
}
Node* p = head;
Node* q = nullptr;
int currentIndex = 0;
while (p != nullptr) {
if (p->next == nullptr) {
return;
}
q = p->next;
if (currentIndex == index - 1) {
p->next = q->next;
delete q;
length--;
break;
}
p = p->next;
currentIndex++;
}
}
clearList()
这个函数可以一次性删除所有结点元素。
实现思路:
- 定义指针q,设为空(nullptr)。
- 定义指针p,将首元结点的地址值传递给p。
- 使用while循环遍历整个链表,每遍历一个结点时,将p的指针域传给q,然后删除p所指向的结点,再把q和p互换。
- 遍历完后,将首元结点设为空(nullptr)。
- 将链表长度设为0
代码段:
void LinkedList::clearList() {
Node* q = nullptr;
Node* p = head;
while (p != nullptr) {
q = p->next;
delete p;
p = q;
}
head = nullptr;
length = 0;
cout << "链表元素已被清空!" << endl;
}
测试案例输出结果:
修改和查找元素
这部分主要包括根据给定的结点修改数据值,根据索引查找数据和根据数据查找索引三种功能的函数。
changeValue()
这个函数可以根据给定的结点修改链表对应结点的数据值。
实现思路:
- 定义指针p,将首元结点的地址值传递给p。
- 定义一个整型变量currentIndex表示当前索引的位置,初始化为0.
- 使用while循环遍历链表,找到结点位置后将该节点的数据域改为传入的数据值。
代码段:
void LinkedList::changeValue(int index, int data) {
Node* p = head;
int currentIndex = 0;
while (p != nullptr) {
if (p->next == nullptr) {
cout << "未找到该索引对应的结点" << endl;
return;
}
if (currentIndex == index) {
p->data = data;
return;
}
p = p->next;
currentIndex++;
}
}
getValue()
这个函数可以根据给定的索引查找并返回对应结点的数据值。
实现思路:
- 定义指针p,将首元结点的地址值传递给p。
- 定义一个整型变量currentIndex表示当前索引的位置,初始化为0.
- 使用while循环遍历链表,找到结点位置后将该节点的数据域作为返回值返回。
代码段:
int LinkedList::getValue(int index) {
Node* p = head;
int currentIndex = 0;
while (p != nullptr) {
if (currentIndex == index) {
return p->data;
}
p = p->next;
currentIndex++;
}
return -1;
}
getIndex()
这个函数可以根据给定的数据查找并返回对应结点第一次出现的索引。
实现思路类似于上面的getIndex(),只是在找到对应结点后返回值是索引值而不是数据值。
代码段:
int LinkedList::getIndex(int data) {
Node* p = head;
int currentIndex = 0;
while (p != nullptr) {
if (p->next == nullptr) {
cout << "没找到该值所在的索引" << endl;
return -1;
}
if (p->data == data) {
return currentIndex;
}
p = p->next;
currentIndex++;
}
return -1;
}
测试案例输出结果:
反转和排序
这部分主要包括链表中的数据反转,升序排序和降序排序。
reverse()
这个函数可以将链表反转。只需要定义三个指针即可实现:
- pre: 指向反转后的链表的头指针
- cur: 指向当前结点的指针
- next:指向当前结点后一位结点的指针
举个例子,假设有一个长度为4的链表,其数据域依次为10,20,30,40。
-
在第一次执行while循环前,指针所指向的结点的示意图如下:
-
第一次循环时:
第一步:next = cur->next
第二步:cur->next = pre
第三步:pre = cur
第四步:cur = next
-
第二次循环后:
-
第三次循环后:
-
第四次循环后:
-
cur指向空,循环结束。
-
最后将pre指向的结点置为新的头结点,就能实现链表的反转。
代码段:
void LinkedList::reverse() {
if (head == nullptr)
{
cout << "链表为空,无法反转" << endl;
return;
}
Node* pre = nullptr;
Node* cur = head;
Node* next = nullptr;
while (cur != nullptr) {
next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
head = pre;
}
sort()
这个函数可以实现链表的升序排序和降序排序,函数的参数设定了个默认值true代表升序排序,如果给形参传入false则该函数实现降序排序。
实现思路:
- 进行判断:如果链表为空,则不进行排序。
- 定义了一个指针p,并将将首元结点的地址值传递给p。
- 使用任意一种算法进行排序,这里简单起见就用了冒泡排序。
- 循环完以后,进行判断:如果形参“ascending”接受的值是false,那么调用上面的reverse()函数,将链表反转,那么链表的降序排序即可实现。
代码段:
void LinkedList::sort(bool ascending) {
if (head == nullptr) {
cout << "链表为空,无法排序" << endl;
return;
}
Node* p;
p = head;
for (int i = 0; i < length - 1; i++) {
while (p->next != nullptr) {
if (p->data > p->next->data) {
int temp = p->data;
p->data = p->next->data;
p->next->data = temp;
}
p = p->next;
}
p = head;
}
if (!ascending) {
this->reverse();
}
}
测试案例输出结果: