目录
具有相同数据的有限序列,首元素没有前驱,尾元素没有后继.其他元素只有一个前驱和后继.
具有相同数据的有限序列,首元素没有前驱,尾元素没有后继.其他元素只有一个前驱和后继.
顺序存储
逻辑上相邻的数据元素,存储在物理上相邻的存储单元中.
优点
存储密度大,可以随机存取表中任意元素
缺点
插入删除需要移动大量元素,浪费空间,不能自由扩充不够灵活.
时空复杂度
时间复杂度为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) | |
适用情况 | 元素个数变化不大,可以事先确定元素个数 删除插入操作少,按位置反问元素多 | 元素个数变化大 需要频繁的删除插入操作 |