数据结构实现(五):链表(C++版)

1. 概念及基本框架

链表 是一种 线性结构 ,而且存储上属于 链式存储(即内存的物理空间是不连续的),是线性表的一种。链表结构如下图所示:
链表
下面以一个我实现的一个简单的链表类来进一步理解链表。

template <class T>
class Node{
public:
	Node(T num = NULL, Node *next = NULL){
		m_data = num;
		this->next = next;
	}
public:
	T m_data;
	Node *next;
};

首先设计一个 结点类 ,这个结点类包含 数据指向下一个结点的指针 。结点类的构造函数可以直接对结点赋值,然后利用结点类对象来创建一个链表。链表类的设计如下:

template <class T>
class LinkedList{
public:
	LinkedList(){
		head.m_data = NULL;
		head.next = NULL;
		m_size = 0;
	}
	...
private:
	Node<T> head;
	int m_size;
};

这里为了避免重复设计就可以兼容更多数据类型,引入了 泛型 ,即 模板 的概念。(模板的关键字是 classtypename
这里的 m_size 表示 链表长度 。为了保护数据,这个变量以及 结点数据 都设置为 private
与动态数组不同,动态数组的“动态”含义是可以自动扩容(缩容),但实质还是静态的,而链表则实现了真正意义上的动态。因为只有需要一个结点,才会添加一个结点,不需要就会删除。在这里,为了下面程序代码编写方便、统一,引入了 虚拟头结点(也称 哨兵结点 )的概念。这个结点本身不存放数据,用户也不知道它的存在。
实现了前面的程序之后,接下来就是一个链表的增、删、改、查以及一些其他基本操作,接下来利用代码去实现。

2. 基本操作程序实现

2.1 增加操作

template <class T>
class LinkedList{
public:
	...
	//增加操作
	void add(int index, T num);
	void addFirst(T num);
	void addLast(T num);
	...
};

首先,在类体内进行增加操作函数的原型说明。这里包括三个函数:
add(添加到任意位置)
addFirst(添加到头部)
addLast(添加到尾部)
然后分别去实现它们。

template <class T>
void LinkedList<T>::add(int index, T num){
	if (index < 0 || index > m_size){
		cout << "添加位置非法!" << endl;
		return;
	}
	Node<T> *node = &head;
	for (int i = 0; i < index; ++i, node = node->next);
	node->next = new Node<T>(num, node->next);
	m_size++;
}
template <class T>
void LinkedList<T>::addFirst(T num){
	add(0, num);
}
template <class T>
void LinkedList<T>::addLast(T num){
	add(m_size, num);
}

由于这些函数在类体外,所以每个函数头部必须添加一行代码:

template <class T>

表示该函数使用模板,下面同理。
如果不使用虚拟头结点,代码编写就要区分第一个结点和其他结点,从这里可以看出引入虚拟头结点的好处,统一了代码编写形式,下面的同理。

2.2 删除操作

template <class T>
class LinkedList{
public:
	...
	//删除操作
	T remove(int index);
	T removeFirst();
	T removeLast();
	void removeElement(T num);
	...
};

同理,在类体内进行删除函数的原型说明。这里包括四个函数:
remove(删除任意位置元素):返回删除元素的值。
removeFirst(删除头部元素):返回删除元素的值。
removeLast(删除尾部元素):返回删除元素的值。
removeElement(删除特定元素):这里删除的是第一个这样的元素,如果想把这样的元素都删掉,可以写一个新的函数来实现。
然后分别去实现它们。

template <class T>
T LinkedList<T>::remove(int index){
	if (index < 0 || index >= m_size){
		cout << "删除位置非法!" << endl;
		return NULL;
	}
	Node<T> *node = &head;
	for (int i = 0; i < index; ++i, node = node->next);
	Node<T> *p = node->next;
	T res = p->m_data;
	node->next = p->next;
	delete p;
	m_size--;
	return res;
}
template <class T>
T LinkedList<T>::removeFirst(){
	return remove(0);
}
template <class T>
T LinkedList<T>::removeLast(){
	return remove(m_size - 1);
}
template <class T>
void LinkedList<T>::removeElement(T num){
	Node<T> *node = &head;
	Node<T> *p;
	while(node){
		p = node->next;
		if (p->m_data == num){
			node->next = p->next;
			delete p;
			m_size--;
			return;
		}
		node = p;
	}
}

这里删除操作的“删除位置非法”后面返回的 NULL 也可以用 throw 抛异常来实现,这里只是为了方便。

2.3 修改操作

template <class T>
class LinkedList{
public:
	...
	//修改操作
	void set(int index, T num);
	...
};

修改操作只有一个函数
set(修改指定位置的值)
同理,在类体内进行删除函数的原型说明,然后在类体外实现。

扫描二维码关注公众号,回复: 4015953 查看本文章
template <class T>
void LinkedList<T>::set(int index, T num){
	Node<T> *node = head.next;
	for (int i = 0; i < index; ++i, node = node->next);
	node->m_data = num;
}

2.4 查找操作

template <class T>
class LinkedList{
public:
	...
	//查找操作
	T get(int index);
	T getFirst();
	T getLast();
	bool contains(T num);
	...
};

查找函数有四个:
get(返回特定位置元素)
getFirst(返回第一个元素)
getLast(返回最后一个元素)
contains(返回是否包含特定元素)
这里并没有实现 find 函数,因为即使获得了元素位置索引,也不能像数组一样方便的再次通过位置索引获得数据,依然需要遍历链表来获得数据。
分别对它们进行实现。

template <class T>
T LinkedList<T>::get(int index){
	if (index < 0 || index >= m_size){
		cout << "访问位置非法!" << endl;
		return NULL;
	}
	Node<T> *node = head.next;
	for (int i = 0; i < index; ++i, node = node->next);
	return node->m_data;
}

template <class T>
T LinkedList<T>::getFirst(){
	return get(0);
}
template <class T>
T LinkedList<T>::getLast(){
	return get(m_size - 1);
}
template <class T>
bool LinkedList<T>::contains(T num){
	Node<T> *node = head.next;
	while(node){
		if (node->m_data == num){
			return true;
		}
		node = node->next;
	}
	return false;
}

同理,这里 get 函数的“访问位置非法”后面返回的 NULL 也可以用 throw 抛异常来实现,这里只是为了方便。

2.5 其他操作

数组还有一些其他的操作,这些函数我在类体内进行了实现。
包括 链表长度 的查询,还有 链表的打印 等操作。

template <class T>
class LinkedList{
public:
	...
	int size(){
		return m_size;
	}
	bool isEmpty(){
		return m_size == 0;
	}
	void print(){
		cout << "LinkedList: ";
		cout << "Size = " << m_size << endl;
		Node<T> *node = head.next;
		while(node){
			cout << node->m_data << "->";
			node = node->next;
		}
		cout << "NULL" << endl;
	}
	...
};

3. 算法复杂度分析

3.1 增加操作

函数 最坏复杂度 平均复杂度
add O(n) O(n/2) = O(n)
addFirst O(1) O(1)
addLast O(n) O(n)

这里的时间复杂度与数组相反,原因在于,引起数组时间复杂度增加的是元素的移动,而引起链表时间复杂度增加的是元素的遍历。

3.2 删除操作

函数 最坏复杂度 平均复杂度
remove O(n) O(n/2) = O(n)
removeFirst O(1) O(1)
removeLast O(n) O(n)

同理,删除操作的时间复杂度与数组也相反。

3.3 修改操作

函数 最坏复杂度 平均复杂度
set O(n) O(n/2) = O(n)

3.4 查找操作

函数 最坏复杂度 平均复杂度
get O(n) O(n/2) = O(n)
getFirst O(1) O(1)
getLast O(n) O(n)
contains O(n) O(n/2) = O(n)

总体情况:

操作 时间复杂度
O(n)
O(n)
O(n)
O(n)

因为链表需要遍历,所以操作的复杂度都是 O(n) 级别的,但是,如果只针对头结点操作,那么操作时间复杂度就会变成 O(1) 级别。

4. 完整代码

程序完整代码(这里使用了头文件的形式来实现类)如下:

#ifndef __LINKEDLIST_H__
#define __LINKEDLIST_H__

using namespace std;

template <class T>
class Node{
public:
	Node(T num = NULL, Node *next = NULL){
		m_data = num;
		this->next = next;
	}
public:
	T m_data;
	Node *next;
};
template <class T>
class LinkedList{
public:
	LinkedList(){
		head.m_data = NULL;
		head.next = NULL;
		m_size = 0;
	}
	int size(){
		return m_size;
	}
	bool isEmpty(){
		return m_size == 0;
	}
	void print(){
		cout << "LinkedList: ";
		cout << "Size = " << m_size << endl;
		Node<T> *node = head.next;
		while(node){
			cout << node->m_data << "->";
			node = node->next;
		}
		cout << "NULL" << endl;
	}
	//增加操作
	void add(int index, T num);
	void addFirst(T num);
	void addLast(T num);
	//删除操作
	T remove(int index);
	T removeFirst();
	T removeLast();
	void removeElement(T num);
	//修改操作
	void set(int index, T num);
	//查找操作
	T get(int index);
	T getFirst();
	T getLast();
	bool contains(T num);
private:
	Node<T> head;
	int m_size;
};

template <class T>
void LinkedList<T>::add(int index, T num){
	if (index < 0 || index > m_size){
		cout << "添加位置非法!" << endl;
		return;
	}
	Node<T> *node = &head;
	for (int i = 0; i < index; ++i, node = node->next);
	node->next = new Node<T>(num, node->next);
	m_size++;
}
template <class T>
void LinkedList<T>::addFirst(T num){
	add(0, num);
}
template <class T>
void LinkedList<T>::addLast(T num){
	add(m_size, num);
}

template <class T>
T LinkedList<T>::remove(int index){
	if (index < 0 || index >= m_size){
		cout << "删除位置非法!" << endl;
		return NULL;
	}
	Node<T> *node = &head;
	for (int i = 0; i < index; ++i, node = node->next);
	Node<T> *p = node->next;
	T res = p->m_data;
	node->next = p->next;
	delete p;
	m_size--;
	return res;
}
template <class T>
T LinkedList<T>::removeFirst(){
	return remove(0);
}
template <class T>
T LinkedList<T>::removeLast(){
	return remove(m_size - 1);
}
template <class T>
void LinkedList<T>::removeElement(T num){
	Node<T> *node = &head;
	Node<T> *p;
	while(node){
		p = node->next;
		if (p->m_data == num){
			node->next = p->next;
			delete p;
			m_size--;
			return;
		}
		node = p;
	}
}

template <class T>
void LinkedList<T>::set(int index, T num){
	Node<T> *node = head.next;
	for (int i = 0; i < index; ++i, node = node->next);
	node->m_data = num;
}

template <class T>
T LinkedList<T>::get(int index){
	if (index < 0 || index >= m_size){
		cout << "访问位置非法!" << endl;
		return NULL;
	}
	Node<T> *node = head.next;
	for (int i = 0; i < index; ++i, node = node->next);
	return node->m_data;
}

template <class T>
T LinkedList<T>::getFirst(){
	return get(0);
}
template <class T>
T LinkedList<T>::getLast(){
	return get(m_size - 1);
}
template <class T>
bool LinkedList<T>::contains(T num){
	Node<T> *node = head.next;
	while(node){
		if (node->m_data == num){
			return true;
		}
		node = node->next;
	}
	return false;
}

#endif

猜你喜欢

转载自blog.csdn.net/qq_35481167/article/details/83927479
今日推荐