§1 线性表
§1.2 单链表
§1.2.1 概览
单链表(singly linked list)是一种最简单的链表表示,也叫做线性链表。单链表通过指针把它的一串存储结点链接成一个链。
单链表的一个存储节点(Node)包含两个部分:数据字段、指针地址(即后继地址)
data | link |
---|---|
数据字段 | 指针地址 |
一个线性表(a0,a1,…,an)的单链表结构如图所示:
其中,链表的第一个结点的地址可以通过链表的头指针first找到,其他结点的地址则在前驱节点的link域中。链表的最后一个结点没有后继,在结点的link域中放一个空指针NULL(即图中的∧)。链表的存储映像如图所示:
单链表可分为带头指针的单链表和带头结点的单链表,如图所示:
§1.2.2 类的定义与实现
// "LinkedList.hpp"
#include <iostream>
#include <stdlib.h>
using namespace std;
// Node
typedef struct Node {
int data;
Node* next;
Node() {
data = 0;
next = NULL;
}
Node(int data) {
this->data = data;
next = NULL;
}
} Node;
// Linked list (head pointer)
class LinkedList {
private:
Node* head;
public:
LinkedList();
LinkedList(LinkedList& other);
~LinkedList();
int getLength() const;
Node* getHead() const;
// Search the location of x, if not found return -1
int search(int x);
// Get and set the value of No.i
int getValue(int i);
void setValue(int i, int x);
// Insert and remove No.i
bool insert(int i, int x);
bool remove(int i);
// Append at the end
void append(int x);
// Determine if it is empty
bool isEmpty() const;
// Output
void output();
// Clear
void clear();
};
LinkedList::LinkedList() {
head = new Node();
}
LinkedList::LinkedList(LinkedList& other) {
/* Wrong Case
Node* temp1 = this->head;
temp1 = new Node();
*/
this->head = new Node();
Node* temp1 = this->head;
Node* temp2 = other.getHead();
while (temp2->next != NULL) {
temp1->next = new Node(temp2->next->data);
// Point to the next one
temp1 = temp1->next;
temp2 = temp2->next;
}
}
LinkedList::~LinkedList() {
clear();
// Finally delete head node
delete head;
head = NULL;
}
int LinkedList::getLength() const {
if (this->head == NULL) return 0;
Node* p = head->next;
int cnt = 0;
while (p != NULL) {
p = p->next;
cnt++;
}
return cnt;
}
Node* LinkedList::getHead() const {
return head;
}
int LinkedList::search(int x) {
Node* p = head->next;
int location = 0;
while (p != NULL) {
if (p->data == x) {
return location;
}
else {
p = p->next;
location++;
}
}
return -1;
}
int LinkedList::getValue(int i) {
if (i >= this->getLength()) {
cerr << "Invalid!!" << endl;
exit(1);
}
Node* p = head->next;
int temp = 0;
while (p != NULL) {
if (temp == i) {
return p->data;
}
else {
p = p->next;
temp++;
}
}
}
void LinkedList::setValue(int i, int x) {
if (i >= this->getLength()) {
cerr << "Invalid!!" << endl;
exit(1);
}
Node* p = head->next;
int temp = 0;
while (p != NULL) {
if (temp == i) {
p->data = x;
return;
}
else {
p = p->next;
temp++;
}
}
}
bool LinkedList::insert(int i, int x) {
if (i >= this->getLength()) {
return false;
}
Node* p = head->next;
Node* pNew = new Node(x);
int temp = 0;
while (p != NULL) {
if (temp == i) {
Node* ptemp = p->next;
p->next = pNew;
pNew->next = ptemp;
return true;
}
else {
p = p->next;
temp++;
}
}
return false;
}
bool LinkedList::remove(int i) {
if (i >= this->getLength()) {
return false;
}
Node* p = head->next;
Node* ptemp = head;
int temp = 0;
while (p != NULL) {
if (temp == i) {
ptemp->next = p->next;
delete p;
return true;
}
else {
ptemp = p;
p = p->next;
temp++;
}
}
return false;
}
void LinkedList::append(int x) {
/* Wrong Case
Node* p = head->next;
Node* pNew = new Node(x);
while(p != NULL) {
p = p->next;
}
p = pNew;
*/
Node* p = head;
Node* pNew = new Node(x);
while (p->next != NULL) {
p = p->next;
}
p->next = pNew;
}
bool LinkedList::isEmpty() const {
if (head == NULL) {
return true;
}
else return false;
}
void LinkedList::output() {
if (this->head == NULL) {
cout << "HEAD -> NULL" << endl;
return;
}
Node* p = head->next;
cout << "HEAD -> ";
while (p != NULL) {
cout << p->data << " -> ";
p = p->next;
}
cout << "NULL" << endl;
}
void LinkedList::clear() {
Node* p;
while (head->next != NULL) {
// Circularly delete head->next
p = head->next;
head->next = p->next;
delete p;
}
}
测试代码:
// main.cpp
#include <iostream>
#include "LinkedList.hpp"
using namespace std;
int main()
{
LinkedList* List1 = new LinkedList();
for (int i = 0; i < 10; i++) {
List1->append(i);
}
List1->output();
List1->insert(2, 0);
List1->remove(7);
List1->output();
LinkedList* List2 = new LinkedList(*List1);
List2->remove(3);
List2->remove(7);
List2->output();
cout << "List2[3] = " << List2->getValue(3) << endl;
cout << "The length of list2 is " << List2->getLength() << endl;
}
测试结果:
HEAD -> 0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> NULL
HEAD -> 0 -> 1 -> 2 -> 0 -> 3 -> 4 -> 5 -> 7 -> 8 -> 9 -> NULL
HEAD -> 0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 7 -> 9 -> NULL
List2[3] = 3
The length of list2 is 8
§1.2.3 对实现代码的分析
- 注意到结点(Node)使用了结构体(struct)类型;分为两种初始化方式,分别是不含参数的构造函数以及含有一个参数的构造函数,这样能够方便单链表进行初始化。
- 在写链表的过程中,一个重点就是new和delete的使用。如"LinkedList.hpp"中第50行的拷贝构造函数,由于C++指针的特性,对于头指针head,必须采用
head = new Node();
的方式进行分配空间(初始化),而不能采用Node* temp1 = this->head; temp1 = new Node();
的方式进行分配空间(初始化)。后者只会分配一个新的空间给指针temp1
,但是与链表本身毫无关系,或者说无法让头节点head
和新的结点相连。
LinkedList::LinkedList(LinkedList& other) {
// WRONG CASE
Node* temp1 = this->head;
temp1 = new Node();
Node* temp2 = other.getHead();
while (temp2->next != NULL) {
temp1->next = new Node(temp2->next->data);
// Point to the next one
temp1 = temp1->next;
temp2 = temp2->next;
}
}
LinkedList::LinkedList(LinkedList& other) {
// CORRECT CASE
this->head = new Node();
Node* temp1 = this->head;
Node* temp2 = other.getHead();
while (temp2->next != NULL) {
temp1->next = new Node(temp2->next->data);
// Point to the next one
temp1 = temp1->next;
temp2 = temp2->next;
}
}
- 与2同样的道理,再比如成员函数
append()
,意图在链表结尾附加一个新的结点。由2可知,上一个结点的next
指针必须指向这个新的结点,故必须采用这样的方法:①创建一个新的结点,其中结点成员data
等于x
;②在通过循环之后,让一个指针p
指向链表最后一个结点;③令p->next
指向新的结点。
void LinkedList::append(int x) {
// WRONG CASE
Node* p = head->next;
Node* pNew = new Node(x);
while(p != NULL) {
p = p->next;
}
p = pNew;
}
void LinkedList::append(int x) {
// CORRECT CASE
Node* p = head;
Node* pNew = new Node(x);
while (p->next != NULL) {
p = p->next;
}
p->next = pNew;
}
- 确保插入和删除时链表的连接状态正常,如图所示:
- 注意析构函数
~LinkedList()
与清空表函数clear()
的区别,其中析构函数~LinkedList()
是先清空表,再删除头结点。
LinkedList::~LinkedList() {
clear();
// Finally delete head node
delete head;
head = NULL;
}
void LinkedList::clear() {
Node* p;
while (head->next != NULL) {
// Circularly delete head->next
p = head->next;
head->next = p->next;
delete p;
}
}
§1.2.4 顺序表与链表的选择
- 顺序表不适用的场合
- 经常插入删除时,不宜使用顺序表
- 线性表的最大长度也是一个重要因素
- 链表不适用的场合
- 当读操作比插入删除操作频率大时,不应选择链表
- 当指针的存储开销,和整个结点内容所占空间相比其比例较大时,
应该慎重选择