【数据结构】第一章 第二节 单链表

§1 线性表

§1.2 单链表

§1.2.1 概览

单链表(singly linked list)是一种最简单的链表表示,也叫做线性链表。单链表通过指针把它的一串存储结点链接成一个链。
单链表的一个存储节点(Node)包含两个部分:数据字段、指针地址(即后继地址)

datalink
数据字段指针地址

一个线性表(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 对实现代码的分析

  1. 注意到结点(Node)使用了结构体(struct)类型;分为两种初始化方式,分别是不含参数的构造函数以及含有一个参数的构造函数,这样能够方便单链表进行初始化。
  2. 在写链表的过程中,一个重点就是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;
	}
}
  1. 与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;
}
  1. 确保插入和删除时链表的连接状态正常,如图所示:
    链表的插入
    链表结点的删除
  2. 注意析构函数~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 顺序表与链表的选择

  1. 顺序表不适用的场合
  • 经常插入删除时,不宜使用顺序表
  • 线性表的最大长度也是一个重要因素
  1. 链表不适用的场合
  • 当读操作比插入删除操作频率大时,不应选择链表
  • 当指针的存储开销,和整个结点内容所占空间相比其比例较大时,
    应该慎重选择
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值