目录
一.顺序表
利用C++来实现顺序表
1.顺序表的声明
#include<iostream>
using namespace std;
#define Type int
#define OK 1
#define NO 0
#define Size 20//顺序表的容量大小
#define Num 2//顺序表的元素个数
//操作顺序表的类
class orderTable
{
private:
int m_size;//顺序表的最大长度
Type* m_table;//用来维护指向顺序表的指针
int m_number;//顺序表的元素个数
public:
//退出
void Exit();
//顺序表的初始化
orderTable(int size);
//析构
~orderTable();
//顺序表的插入
int Insert(int n,int x);
//顺序表的查找
int Find(int n);
//顺序表的删除
int Delete(int n);
};
2. 退出功能
//退出
void orderTable::Exit()
{
exit(0);
}
3.初始化和析构
//顺序表的初始化
orderTable::orderTable(int size)
{
//初始化元素个数
this->m_number =Num;
//初始化最大长度
this->m_size = size;
//在堆内存创建顺序表
this->m_table = new int(size);
}
//析构
orderTable::~orderTable()
{
if (this->m_table != NULL)
{
delete m_table;
m_table = NULL;
}
}
堆区空间需要在析构中释放
4.插入功能
//顺序表的插入
//n为元素下标,x为元素
int orderTable::Insert(int n,int x)
{
//判断顺序表是否已满
if (this->m_number == this->m_size)
{
cout << "顺序表已满,请重新操作" << endl;
return NO;
}
//判断顺序表插入位置是否合理
if (n<-1 || n>this->m_number - 1)
{
//不是小于0而是小于-1是因为不仅可以在后面插入,也可以在前面插入
cout << "插入元素下标错误,请重新操作" << endl;
return NO;
}
//将插入的元素后面的元素全部往后移
//从最后一个元素开始才能保证后移过程元素不被覆盖
for (int j = this->m_number - 1; j > n; j--)
{
this->m_table[j + 1] = this->m_table[j];
}
//将元素插入
this->m_table[n + 1] = x;
cout << this->m_table[n + 1] << endl;
//将元素个数加一
this->m_number++;
return OK;
}
每一次插入都要做是否越界检查,顺序表插入算法的平均时间复杂度为O(n)
5.查找功能
//查找
int orderTable::Find(int n)
{
if (n<-1 || n>this->m_number - 1)
{
cout << "下标输入错误,请重新输入" << endl;
return NO;
}
cout << this->m_table[n] << endl;
return OK;
}
6.删除功能
//删除
int orderTable::Delete(int n)
{
if (this->m_number == 0)
{
cout << "顺序表为空" << endl;
return NO;
}
if (n<-1 || n>this->m_number - 1)
{
cout << "下标输入错误,请重新输入" << endl;
return NO;
}
//将要删除的元素被后面的元素替代
for (int j = n; j < this->m_number - 2; j++)
{
this->m_table[j] = this->m_table[j + 1];
}
this->m_number--;
return OK;
}
顺序表的删除先要判断是否为空,删除下标是否越界,然后采用逻辑删除(即直接覆盖)删除
删除算法的平均时间复杂度为O(n)
7.顺序表的优缺点
1.顺序表较于链表可以直接查找到想要的元素,即直接m_table[n] 访问,而链表需要通过指针域一个一个找,则到找到,所以顺序表的查找时间复杂度为O(1),而链表为O(n)n为查找下标
2.顺序表的插入和删除都要移动大量的元素,时间复杂度为O(n),而链表通过指针直接删除和插入,时间复杂度为O(1),所以当需要大量删除和插入操作时链表效率更高
二.链表
1.声明
#include<iostream>
using namespace std;
#define OK 1
#define NO 0
#define Type int
class List
{
public:
//数据域
Type element;
//指针域
List* m_link;
};
class singleList
{
public:
//头指针
List* first;
//链表元素个数
int n;
};
//管理类
class Manager
{
public:
//初始化链表
int Init(singleList& L);
//查找
int Find(int x, singleList& L);
//插入
int Insert(int x, Type date, singleList& L);
//删除
int Delete(int x, singleList& L);
//输出
int outPut(singleList& L);
};
由于链表需要用到头指针,所以需要两个类,头指针first来指向链表内的元素(即指向链表的类)
链表具有数据域和指针域,该元素的指针域指向链表里的下一个元素,所以指针域的指针指向自身
对类的传递都使用引用传递,既简单又对类本身操作(不需要复制类的副本)
2.链表的初始化
//初始化
int Manager::Init(singleList& L)
{
L.first = NULL;
L.n = 0;
return OK;
}
初始化只需要对类sigleList进行操作,不需要对链表的数据域和指针域初始化,因为元素个数n为0
3.插入功能
//插入(x为插入的下标,date为插入的元素)
int Manager::Insert(int x, Type date, singleList& L)
{
List* p;
p = L.first;//需要两个指针,头指针移动到被插入节点的位置,新建一个指针来指向插入元素
List* q;//将新建指针作为插入元素的指针域,所以直接指向链表类
//插入
//先找到插入下标的指针域
for (int i = 0; i < x; i++)
{
p = p->m_link;
}
//创建一个新的节点
q = new(List);//新节点的指针域
q->element = date;//新节点的数据域
//插入在p指向的节点后
if (x > -1)
{
q->m_link = p->m_link;
p->m_link = q;
}
//插入在头节点前成为新的头节点
else
{
q->m_link = L.first;
L.first = q;
}
L.n++;
return OK;
}
链表的插入需要注意两种情况,插入的是链表头节点后还是头节点前,头节点前则需要作为新的头节点
4.查找功能
//查找(x为查找元素的下标)
int Manager::Find(int x, singleList& L)
{
if (x<0 || x>L.n-1)
{
return NO;
}
List* p;
p = L.first;
//由p进行一个节点一个节点查找
for (int i = 0; i < x; i++)
{
p = p->m_link;
}
Type date = p->element;
return date;
}
查找只需要注意是否越界
5.删除功能
//删除
int Manager::Delete(int x, singleList& L)
{
if (L.n==0)
{
return NO;
}
List* p;
p = L.first;
List* q;
q = L.first;//两个指针都从头节点开始移动,分别找到各自相应位置,链表必须要两个指针操作
//找到删除的节点的前一个节点
for (int i = 0; i < x-1; i++)
{
p = p->m_link;
}
//找到删除的节点
for (int j = 0; j < x; j++)
{
q = q->m_link;
}
//删除的是头节点
if (x == 0)
{
L.first = L.first->m_link;//头节点的指针域指向下一个值,将头指针指向下一个值
}
else
{
p->m_link = q->m_link;//直接将被删元素指针域赋给前一个节点,前一个节点直接指向被删元素
//下一个节点,即逻辑上的删除
}
delete q;
L.n--;
return OK;
}
6.输出功能
//输出
int Manager::outPut(singleList& L)
{
List* p;
p = L.first;
if (L.n == 0)
{
cout << "链表为空" << endl;
}
else
{
for (int i = 0; i < L.n - 1; i++)
{
cout << p->element << " ";
p = p->m_link;
}
}
return OK;
}
7.带表头节点的单链表
带表头节点的链表可以不用考虑插入和删除时是头节点的情况,具有更高效率
(1)初始化
class List
{
public:
//数据域
Type element;
//指针域
List* m_link;
};
class singleList
{
public:
//表头指针
List* head;
//链表元素个数
int n;
};
//初始化
int Manager::Init(singleList& L)
{
L.head=new(list);
L.head->m_list=NULL;//表头节点的数据域不存放元素,所以不需要置为空
//指针域指向下一个元素,则需要置为空
L.n = 0;
return OK;
}
(2)插入
//插入(x为插入的下标,date为插入的元素)
int Manager::Insert(int x, Type date, singleList& L)
{
List* p;
p = L.head;
List* q;
//插入
//先找到插入下标的指针域
for (int i = 0; i <= x; i++)//表头节点的插入和头节点的插入的区别就在于i<x(头节点)
//i<=x(表头节点),因为表头节点不占用下标为0的元素
{
p = p->m_link;
}
//创建一个新的节点
q = new(List);//新节点的指针域
q->element = date;//新节点的数据域
//插入在p指向的节点后
if (x > -1)
{
q->m_link = p->m_link;
p->m_link = q;
}
L.n++;
return OK;
}
(3)删除
//删除
int Manager::Delete(int x, singleList& L)
{
if (L.n==0)
{
return NO;
}
List* p;
p = L.head;
List* q;
//找到删除的节点的前一个节点
for (int i = 0; i < x; i++)//若是i<=x,则p为删除的节点
//若在头节点中,i<x为删除的节点
{
p = p->m_link;
}
q=p->m_link;//删除的节点
if(x>-1)
{
p->m_link=q->m_link;
}
delete q;
L.n--;
return OK;
}
带表头节点的链表与头节点链表的区别就是i<x(头节点)和i<=x(表头节点),即表头指针移动的次数比头指针移动到目标节点的次数多一次,因为不占用0下标
8.单循环链表
//插入(x为插入的下标,date为插入的元素)
int Manager::Insert(int x, Type date, singleList& L)
{
List* p;
p = L.head;
List* q;
//插入
//先找到插入下标的指针域
for (int i = 0; i <= x; i++)/
{
p = p->m_link;
}
//创建一个新的节点
q = new(List);//新节点的指针域
q->element = date;//新节点的数据域
//插入在p指向的节点后
if (x == n)//最后一个元素
{
q->m_link=head;
p->m_link=q;
else if (x > -1 )//非最后一个元素
{
q->m_link = p->m_link;
p->m_link = q;
}
L.n++;
return OK;
}
单循环链表指的就是最后一个元素的指针域不为空,指向第一个元素(即表头节点),形成一个单向循环,所以只需分两种情况:插入的是否为最后一个元素便可以实现
9.双向链表
双向链表是为了解决不仅可以从前往后查询,还可以从后往前查询(查靠后元素便用从后往前,效率比起只能单向高)
(1)插入
class List
{
public:
//数据域
Type element;
//指针域
List* L_m_link;//左指针
List* R_m_link;//右指针
};
//插入(x为插入的下标,date为插入的元素)
int Manager::Insert(int x, Type date, singleList& L)
{
List* p;
p = L.head;
List* q;
//插入
//先找到插入下标的后一个元素的指针域
for (int i = 0; i <= x+1; i++)
{
p = p->R_m_link;//可以根据左或右指针域查找,右为从前往后,左为从后往前
}
//创建一个新的节点
q = new(List);//新节点的指针域
q->element = date;//新节点的数据域
//插入在p指向的节点前
if (x > -1)
{
q->L_m_link=p->L_m_link;//先将后一个的左指针给插入元素,让插入元素的左指针指向前一个元素
q->R_m_link=p;//再将插入元素的右指针指向后一个元素
p->L_m_link->R_m_link=q;//然后将前一个元素的右指针指向插入元素
p->L_m_link=q;//最后将后一个元素的左指针指向插入元素
}
L.n++;
return OK;
}
插入的核心为将p指针的指针域利用完再对其覆盖,p指针指向插入元素的后一个元素,所以其左指针肯定指向插入元素,右指针与插入元素无关,所以需要先利用p指针的左指针域,再利用插入元素的前一个元素的右指针域
(2)删除
//删除
int Manager::Delete(int x, singleList& L)
{
if (L.n==0)
{
return NO;
}
List* p;
p = L.head;
//找到删除的节点
for (int i = 0; i <= x; i++)
{
p = p->R_m_link;//从前往后查找
}
if(x>-1)
{
p->L_m_link->R_m_link=p->R_m_link;//将删除元素的前一个元素的右指针域指向删除元素的后一个元素
p->R_m_link->L_m_link=p->L_m_link;//将删除元素的后一个元素的左指针域指向删除元素的前一个元素
}
delete q;
L.n--;
return OK;
}