数据结构-线性表

目录

具有相同数据的有限序列,首元素没有前驱,尾元素没有后继.其他元素只有一个前驱和后继.

顺序存储

优点

缺点

时空复杂度

sequence_list.hpp

main.cpp

链式存储

优点

缺点

链表分类

单链表

双链表

循环链表

头指针

头结点

单链表

linear_list.hpp

main.cpp

循环链表(由于操作都差不多就不敲了,主要是懒)

双向链表

链表时间复杂度

顺序表和线性表比较


具有相同数据的有限序列,首元素没有前驱,尾元素没有后继.其他元素只有一个前驱和后继.

顺序存储

逻辑上相邻的数据元素,存储在物理上相邻的存储单元中.

优点

存储密度大,可以随机存取表中任意元素

缺点

插入删除需要移动大量元素,浪费空间,不能自由扩充不够灵活.

时空复杂度

时间复杂度为O(n)

空间复杂度为O(1)

sequence_list.hpp

#pragma once

#include <iostream>

using namespace std;

const int MAX_SIZE = 100;

template<class T>
class SequenceList
{
public:
	SequenceList();											//构造函数初始化
	~SequenceList();										//析构函数销毁空间
	void clear();											//清空顺序表
	int getSize()const;										//得到顺序表长度
	bool isEmpty()const;									//判断顺序表是否为空
	bool getElement(const int& pos, T& save_element);		//按照位置得到元素,元素存储在save_element中
	int locateElement(T& element);							//按元素得到第一次出现的位置,失败返回0,成功返回元素在顺序表中的位置
															//计算平均查找效率(期望值),是线代中的概率论,(n+1)/2
	bool insert(const T& new_element, const int& pos);		//插入元素在pos之前,插入成功长度+1
															//期望值为,n/2
	bool deleted(const int& pos);							//按位置删除元素
															//期望值为,(n-1)/2
	void uniond(SequenceList<T>& sl);						//并集
	void show()const;
private:
	T* m_arr;
	int m_size;
};

template<class T>
inline SequenceList<T>::SequenceList()
{
	this->m_arr = new T[MAX_SIZE]{ 0 };	//其实new很牛,只要堆上空间够就不存在分配失败
	if (!this->m_arr)
	{
		cout << "overflow" << endl;
		exit(1);
	}

	this->m_size = 0;
}

template<class T>
inline SequenceList<T>::~SequenceList()
{
	if (this->m_arr)
	{
		delete[] this->m_arr;
	}
}

template<class T>
inline void SequenceList<T>::clear()
{
	this->m_size = 0;
}

template<class T>
inline int SequenceList<T>::getSize()const
{
	return this->m_size;
}

template<class T>
inline bool SequenceList<T>::isEmpty()const
{
	if (this->m_size == 0)
	{
		return true;
	}
	return false;
}

template<class T>
inline bool SequenceList<T>::getElement(const int& pos, T& save_element)
{
	if (pos > this->m_size || pos < 1)
	{
		cout << "invalid location" << endl;
		return false;
	}

	save_element = this->m_arr[pos - 1];

	return true;
}

template<class T>
inline int SequenceList<T>::locateElement(T& element)
{
	for (int i = 0; i < this->getSize(); i++)
	{
		if (element == this->m_arr[i])
		{
			return i + 1;
		}
	}

	return 0;
}

template<class T>
inline bool SequenceList<T>::insert(const T& new_element, const int& pos)
{
	//一个元素,MAX_SIZE为1时,顺序表已满
	if (this->m_size == MAX_SIZE)
	{
		cout << "sequence list is full" << endl;
		return false;
	}

	//pos<1 插入不能为0或负数
	//pos>this->m_size + 1
	//  1      0    0 0 0  ...
	//size  size+1
	if (pos < 1 || pos>this->m_size + 1)
	{
		cout << "invalid location" << endl;
		return false;
	}

	//一个元素,pos为1时
	// 1    0 0 0 ...
	//i-1 = 1
	//  0   1 0 0 0 ...
	//pos-1
	for (int i = this->m_size; i >= pos; i--)
	{
		this->m_arr[i] = this->m_arr[i - 1];
	}

	m_arr[pos - 1] = new_element;

	this->m_size++;

	return true;
}

template<class T>
inline bool SequenceList<T>::deleted(const int& pos)
{
	if (this->m_size == 0)
	{
		cout << "sequence list is empty" << endl;
		return false;
	}

	if (pos < 1 || pos>this->m_size)
	{
		cout << "invalid location" << endl;
		return false;
	}

	for (int i = pos - 1; i < this->m_size - 1; i++)
	{
		this->m_arr[i] = this->m_arr[i + 1];
	}

	this->m_size--;

	return true;
}

template<class T>
inline void SequenceList<T>::uniond(SequenceList<T>& sl)
{
	int len = sl.getSize();
	int len2 = this->getSize();
	T element;

	for (int i = 1; i <= len; i++)
	{
		sl.getElement(i, element);
		if (!this->locateElement(element))
		{
			this->insert(element, ++len2);
		}
	}
}

template<class T>
inline void SequenceList<T>::show() const
{
	for (int i = 0; i < this->m_size; i++)
	{
		cout << this->m_arr[i] << " ";
	}
	cout << endl;
}

main.cpp

#include "sequence_list.h"

int main()
{
	SequenceList<int> sl;
	if (sl.isEmpty())
	{
		cout << "sl is empty" << endl;			//会执行
	}

	sl.insert(1, 1);
	sl.insert(2, 2);
	sl.insert(3, 3);
	sl.insert(4, 4);
	sl.insert(5, 5);
	sl.insert(5, 7);							//invalid location
	sl.insert(5, 0);							//invalid location
	cout << sl.getSize() << endl;				//5
	sl.show();									//1 2 3 4 5

	sl.deleted(3);
	sl.show();									//1 2 4 5
	sl.deleted(1);
	sl.show();									//2 4 5
	sl.deleted(3);								
	sl.show();									//2 4
	sl.deleted(0);								//invalid location
	sl.deleted(3);								//invalid location
	cout << sl.getSize() << endl;				//2

	int val = 0;
	sl.getElement(2,val);						
	cout << val << endl;						//4

	SequenceList<int> sl2;
	sl2.insert(1, 1);
	sl2.insert(2, 2);
	sl2.insert(3, 3);
	sl2.insert(4, 4);
	sl2.insert(5, 5);
	sl.uniond(sl2);							
	sl.show();									//2 4 1 3 5

	sl.clear();
	sl.show();
	cout << sl.getSize() << endl;				//0

	return 0;
}

链式存储

元素在存储中是任意的,逻辑上相邻的元素,在物理上不一定相邻

链式存储的数据元素(结点)由两部分构成,数据域(用来存储数据)和指针域(用来存储下一个数据元素的地址)

优点

可以动态的申请空间和释放,删除插入不需要移动元素

缺点

存储密度小,指针域造成额外空间,非随机存取,对结点操作需要遍历

链表分类

单链表

只有一个指针域的链表,指向后继

双链表

有两个指针域的链表,一个指向前驱,一个指向后继

循环链表

首尾相接的链表,尾结点指向头结点

头指针

指向第一个结点的指针

头结点

链表之外首结点之前的一个结点,方便统一删除插入操作(可以使每个结点都有前驱)

单链表

linear_list.hpp

#pragma once

#include <iostream>

using namespace std;

//数据元素,链表中存储的元素(结点),包含两部分,数据域和指针域
template <class T>
class Node
{
public:
	T m_data;										//数据域
	Node<T>* m_next;								//指针域,要指向下一结点地址,所以类型要和下一结点相同
	Node()											//构造函数,初始化结点
	{
		m_next = nullptr;
	}
};

template <class T>
class LinkedList
{
public:
	LinkedList();											//构造函数,初始化
	~LinkedList();											//析构函数销毁函数
	bool isEmpty()const;									//判断链表是否为空
	void clear();											//清空链表,删除所有元素,头结点重新指向空
	int getSize()const;										//得到链表长度
	bool getElement(const int& pos, T& save_element)const;	//按位置得到元素,元素保存在save_element中
	Node<T>* locateElement(const T& element)const;			//按元素得到结点的地址,失败返回nullptr
	int locateElement(const T& element, int)const;			//按元素得到结点的位置,失败返回0
															//时间复杂度为O(n)
	bool insert(const int& pos, const T& new_element);		//按位置插入新结点
															//时间复杂度为O(n)
	void frontInsert(const T& new_element);					//前插
															//时间复杂度为O(1)
	void backInsert(const T& new_element);					//尾插
															//时间复杂度为O(n)
	bool deleted(const int& pos);							//按位置删除结点
															//时间复杂度为O(n)
	void show()const;										//遍历
private:
	Node<T>* m_head;
};

template<class T>
inline LinkedList<T>::LinkedList()
{
	//会调用node类的构造函数
	//其实就是在内存中找一个区域构造头结点嘛,然后将头结点的后继指向空
	m_head = new Node<T>;

	if (!m_head)
	{
		cout << "overflow" << endl;
		exit(1);
	}
}

template<class T>
inline LinkedList<T>::~LinkedList()
{
	//从头结点开始释放
	//其实这个东西,就先写个head然后释放掉,其他的逻辑哪里不对缺啥加啥呗.反正我一直都这么写的,刚学的时候不理解留下来的习惯
	Node<T>* head_next = nullptr;
	while (this->m_head)
	{
		head_next = this->m_head->m_next;	//然后写这个,保留下一节点
		delete this->m_head;				//先写这个,发现无法访问下一结点,所以先保存下一节点
		this->m_head = head_next;			//最后写这个,条件后移
	}
}

template<class T>
inline bool LinkedList<T>::isEmpty() const
{
	//头结点的下一节点为空就是空链表
	if (m_head->m_next)
	{
		return false;
	}
	return true;
}

template<class T>
inline void LinkedList<T>::clear()
{
	Node<T>* current = m_head->m_next;
	Node<T>* current_next = nullptr;

	while (current)
	{
		current_next = current->m_next;
		delete current;
		current = current_next;
	}

	this->m_head = nullptr;
}

template<class T>
inline int LinkedList<T>::getSize() const
{
	if (this->m_head == nullptr)
	{
		return 0;
	}

	Node<T>* current = this->m_head->m_next;
	int count = 0;

	while (current)
	{
		count++;
		current = current->m_next;
	}

	return count;
}

template<class T>
inline bool LinkedList<T>::getElement(const int& pos, T& save_element)const
{
	if (pos<1 || pos>getSize())
	{
		cout << "invalid location" << endl;
		return false;
	}

	int count = 1;
	Node<T>* current = this->m_head;

	while (count <= pos)
	{
		current = current->m_next;
		count++;
	}

	save_element = current->m_data;

	return true;
}

template<class T>
inline Node<T>* LinkedList<T>::locateElement(const T& element) const
{
	Node<T>* current = m_head->m_next;
	while (current)
	{
		if (current->m_data == element)
		{
			return current;
		}
		current = current->m_next;
	}
	return nullptr;
}

template<class T>
inline int LinkedList<T>::locateElement(const T& element, int) const
{
	Node<T>* current = m_head->m_next;
	int count = 1;
	while (current && current->m_data != element)
	{
		count++;
		current = current->m_next;
	}

	if (current)
	{
		return count;
	}
	else
	{
		return 0;
	}
}

template<class T>
inline bool LinkedList<T>::insert(const int& pos, const T& new_element)
{
	if (pos < 1 || pos>this->getSize() + 1)
	{
		cout << "invalid location" << endl;
		return false;
	}

	Node<T>* current = m_head;
	Node<T>* new_node = new Node<T>;
	new_node->m_data = new_element;
	int count = 1;

	while (current && count < pos)
	{
		current = current->m_next;
		count++;
	}

	if (!current || count > pos)
	{
		return false;
	}

	new_node->m_next = current->m_next;		//然后再写这个
	current->m_next = new_node;				//和释放一样我都是先把这个写了,发现没法指向下一结点

	return true;
}

template<class T>
inline void LinkedList<T>::frontInsert(const T& new_element)
{
	Node<T>* new_node = new Node<T>;
	new_node->m_data = new_element;

	new_node->m_next = m_head->m_next;
	m_head->m_next = new_node;
}

template<class T>
inline void LinkedList<T>::backInsert(const T& new_element)
{
	//这个其实可以保留尾指针来实现 那么时间复杂度也是O(1)
	Node<T>* new_node = new Node<T>;
	new_node->m_data = new_element;
	Node<T>* current = m_head->m_next;
	Node<T>* prev = m_head;

	while (current)
	{
		current = current->m_next;
		prev = prev->m_next;
	}

	prev->m_next = new_node;
}

template<class T>
inline bool LinkedList<T>::deleted(const int& pos)
{
	if (pos < 1 || pos>this->getSize())
	{
		cout << "invalid location" << endl;
		return false;
	}

	//保留前一结点,其实这个画个图就很明显了.
	//需要前一结点的下一结点指向被删除的下一节点
	Node<T>* prev = m_head;
	Node<T>* current = m_head->m_next;
	int count = 1;

	while (current && pos > count)
	{
		current = current->m_next;
		prev = prev->m_next;
		count++;
	}

	if (!current || pos < count)
	{
		return false;
	}

	prev->m_next = current->m_next;

	//不要忘记释放
	delete current;

	return true;
}

template<class T>
inline void LinkedList<T>::show() const
{
	Node<T>* current = this->m_head->m_next;

	while (current)
	{
		cout << current->m_data << " ";
		current = current->m_next;
	}
	cout << endl;
}

main.cpp

#include "linked_list.h"

int main()
{
	LinkedList<int> ll;

	if (ll.isEmpty())
	{
		cout << "is empty" << endl;		//会执行
	}

	ll.frontInsert(1);	//1
	ll.frontInsert(2);	//2 1
	ll.backInsert(2);	//2 1 2
	ll.backInsert(3);
	ll.show();			//2 1 2 3
	cout << ll.getSize() << endl;	//4

	ll.insert(1, 1);	//1 2 1 2 3
	ll.insert(6, 4);	//1 2 1 2 3 4
	ll.insert(3, 3);
	ll.show();			//1 2 3 1 2 3 4
	cout << ll.getSize() << endl;	//7

	int ret = 0;
	ll.getElement(4, ret);
	cout << ret << endl;	//1
	Node<int>* ret2 = ll.locateElement(3);
	cout << ret2->m_data << endl;	//3
	cout << ll.locateElement(4, 1) << endl;	//7

	ll.deleted(4);
	ll.show();		//1 2 3 2 3 4
	cout << ll.getSize() << endl;	//6

	ll.clear();
	cout << ll.getSize() << endl;	//0

	return 0;
}

循环链表(由于操作都差不多就不敲了,主要是懒)

就是尾指针指向头结点(注意头结点和头指针的区别)

头结点的指针域指向自己(空表)

head->next = head;

单链表时为空是最后一个结点,循环链表判断是否等于头指针(结束条件)(这样时间复杂度为O(1))

//单链表 
current = head->next; 
while(current) 
{ 
    current = current->next; 
} 
//单循环链表 
current = head->next; 
while(current != head) 
{ 
    cout<<current->data<<endl; 
    current = current->next; 
}

经常操作首尾元素的话可以建立尾指针(头结点是链表外的结点,头指针是指向链表第一个元素的指针,首元素是链表中的第一个元素,尾部同理)

//一个next是头结点,两个是首元素,取的是首元素的数据域 
tail->next->next->data; 
//最后一个元素 
tail->data;

双向链表

就是在单链表中每个结点里增加一个指向前一个结点的指针域.

好处就是单链表找前驱的时候时间复杂度为O(n),双链表是O(1)

next和prev都指向空时就是空表

双向循环链表就是next和prev都指向自己(空表)

双向链表有对称性

//current为一个结点 
current->prev->next == current == current->next->prev;

双向链表插入,这个伪代码也不好表示.其实在双向链表中不存在找不到的前驱后继,插入的结点前后个需要链接两个,也就是一共四条指令.

//new_node新节点 current为当前结点位置 
//current是要插入的后一结点位置(前插),新结点的上一结点指向前一个结点 
new_node->prev = current->prev; 
//将前一结点的下一结点指向新节点 
current->prev->next = new_node; 
//将新节点的下一结点指向当前结点 
new_node->next = current; 
//将当前结点的的前一结点指向新节点 
current->prev = new_node;

双向链表删除,就是当前结点的前驱指向后继,后继指向前驱,也就是两天指令

//current为待删除结点 
//前一结点的下一个结点指向待删除结点的下一结点 
current->prev->next = current->next; 
//下一结点的前移结点指向待删除结点的前一结点 
current->next->prev = current->prev; 
//不要忘记释放 
delete current;

链表时间复杂度

找首元素(首元结点)

找尾结点(尾结点是空一样在链表之外不存元素)

找一个结点的前驱结点

带头结点的单链表

头结点的下一节点O(1)

遍历O(n)

无法获取

单循环链表(头指针)

头指针O(1)

遍历O(n)

循环一圈O(n)

单循环链表(尾指针)

尾指针的下一节点O(1)

尾指针O(1)

循环一圈O(n)

双向循环链表

头结点的下一结点O(1)

头结点的前一结点O(1)

prev直接之O(1)

顺序表和线性表比较

顺序表

链表

空间

存储空间

先分配空间,会造成浪费或溢出

动态分配,不会找出空间浪费或溢出

存储密度

不会因为元素逻辑关系造成额外开销,存储密度为1

需要使用指针来表示元素之间的逻辑关系,存储密度小于1

时间

存取元素

随机存取,按位置访问元素时间复杂度为O(1)

顺序存取,按位置访问元素时间复杂度为O(n)

删除,插入

需要大量移动元素,时间复杂度为O(n)

不需要移动元素,确定位置后时间复杂度为O(1)

适用情况

元素个数变化不大,可以事先确定元素个数

删除插入操作少,按位置反问元素多

元素个数变化大

需要频繁的删除插入操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值