链表和顺序表的区别
链表:动态存储
顺序表:静态存储
时间性能上
空间性能上
链表的分类
单链表
逻辑描述
存储空间分配特点
1 物理上不连续,动态存储
2 依赖后继指针定位下一个元素的位置
链表的结点
数据域:存放数据
指针域:存放后继结点的位置
//C++中用结构类型描述结点
template <class T>
struct Node
{
T data;
Node<T> *next;
};
分类
1.带头指针的单链表:
(1)缺点1:空链表、非空链表的处理不同
(2)缺点2:插入、删除首结点的处理和其他结点不同
2.带头结点的单链表:
(1)优点:插入、删除不需要移动大量元素;动态分配内存
(2)缺陷:顺序查找,不能直接定位
举例
1 生成首结点:
Node<T> *p= new Node<T>
2 为结点赋值:
p->data= ‘a’;
p->next = NULL; //(尾结点)
// 或者 p->next = (其他结点的地址);
3 头结点指向首结点
front->next = p;
4 释放结点:
delete p;
front->next =NULL;
实现
template <class T>
class LinkList
{
public:
LinkList(); //无参构造函数
LinkList(T a [], int n); //有参构造函数,使用含有n个元素的数组a初始化单链表
~LinkList(); //析构函数
void PrintList(); //按次序遍历线性表中的各个数据元素
int GetLength(); //获取线性表的长度
Node <T> * Get(int i); //获取线性表第i个位置上的元素结点地址
int Locate (T x); //查找线性表中值为x的元素,找到后返回其位置
void Insert (T x ,int i);//在线性表的第i个位置上插入值为x的新元素
T Delete(int i); //删除线性表第i个元素,并将该元素返回
private:
Node <T> * front; //头指针
};
建立单链表——构造函数
无参构造函数——空链表
template <class T>
LinkList<T>::LinkList()
{
front = new Node<T>;
front->next = NULL;
}
含参构造函数
LinkList(T a [], int n)
使用长度为n的数组a来初始化单链表
建立单链表的两种方法:头插法和尾插法
(1)头插法:
template <class T>
LinkList<T>::LinkList(T a[], int n)
{ //初始化头结点
front = new Node<T>; //在堆中建立新结点
front->next = NULL;
for (int i=0; i<n; i++)
{
Node<T> *s=new Node<T>;
s->data=a[i]; //生成新结点
s->next = front->next;
front->next =s;// 链接在头结点和首结点之间
}
}
(2)尾插法:
template <class T>
LinkList<T>::LinkList(T a[], int n)
{
front = new Node<T>;
Node<T> *r = front;//初始化头结点
for (int i=0; i<n; i++)
{
Node<T> *s=new Node<T>;
s->data=a[i];
s->next = NULL;// 生成新结点
r->next = s;// 链接在尾结点后面
r= s;// 尾指针后移
}
}
析构函数
链表使用完毕,必须要释放内存
template <class T>
LinkList<T>::~LinkList() //析构函数
{
Node<T> *p=NULL; //初始化工作指针p
while (front!=NULL ) //要释放的结点存在
{
p=front->next; //暂存要释放的结点
delete front; //释放结点
front = p; //移动工作指针
}
}
查找
按位查找
查找第i个结点的数据元素的值
伪代码
1.初始化工作指针p和计数器j,p指向第一个结点,j=1;
2.循环以下操作,直到p为空或j等于i
(1) p指向下一个结点;
(2) j加1;
3.返回p;
返回地址:
template <class T>
Node <T> *LinkList<T>:: Get(int i)
{
Node <T> * p = front->next; //初始化工作指针
int j = 1; //初始化计数器
while (j!=i) && (p!=NULL) //两个条件都满足,则继续循环
{
p = p->next; //工作指针后移
j++;
}
if(p==NULL) throw “位置非法”;
return p; //查找到第i个元素或没找到
}
返回值:
template <class T>
T *LinkList<T>:: Get(int i)
{
Node<T> *p=front->next; //初始化工作指针
int j=1; //初始化计数器
while (j!=i) && (p!=NULL) //两个条件都满足,则继续循环
{
p=p->next; //工作指针后移
j++;
}
if(p==NULL) throw “位置非法”;
return p->data;
}
按值查找
查找给定值的结点,找到后返回元素地址或序号
template <class T>
int LinkList<T>::Locate (T x)
{
Node <T> * p = front->next; //初始化工作指针
int j=1;
while (p)
{
if (p->data == x) return j;//找到被查元素,返回位置(注意“==”一定情况下要运算符重载)
p = p->next;
j++;
}
return -1; //若找不到,返回错误标识-1
}
插入
//在线性表的第i个位置上插入值为x的新元素
template <class T>
void LinkList<T>::Insert(T x ,int i)
{
Node <T> * p = front; //初始化工作指针
//若不是在第一个位置插入,得到第i-1个元素的地址。
if (i != 1) p = Get(i-1);
if (p!=NULL)
{
Node <T> * s = new Node <T>;//建立新结点
s->data = x;
s->next = p->next;
p->next = s; //将新结点插入到p所指结点的后面
}
else throw "插入位置错误";
}
改进:给定元素P前插操作:时间复杂度由O(n)变为O(1)
(1)在堆中建立新结点:Node < T> * s = new Node < T>;
(2)将p结点的数据域写入到新结点的数据域:s->data = p->data;
(3)修改新结点的指针域: s->next = p->next;
(4)修改p结点的指针域,将新结点加入到链表中:p->next = s;
(5)将x写入到p结点的数据域:p->data=x;
Node < T> * s = new Node < T>;
s->data = p->data;
s->next = p->next;
p->next = s;
p->data=x;
举一反三
已知L是带头结点的单链表,且P结点既不是首结点,也不是尾结点,试从下列语句中选择合适的序列。
1 在P结点后插入S结点
S->next=P->next;
P->next=S;
2 在P结点前插入S结点
Q=P;
P=L;
while(P->next!=Q)
P=P->next;
S->next=P->next;
P->next=S;
3 在表首插入S结点的序列
P=L;
S->next=P->next;
P->next=S;
4 在表尾插入S结点的序列
while(P->next!=NULL)
P=P->next;
S->next=NULL;
P->next=S;
删除
伪代码
从第一个结点开始,查找第i-1个元素,设为p指向该结点;
若删除位置错误(p为空或p->next为空),抛出异常
设q指向第i个元素:q = p->next;
摘链,即将q元素从链表中摘除:p->next = q->next;
保存q元素的数据:x = q->data;
释放q元素:delete q;
template <class T>
T LinkList<T>::Delete(int i)
{
Node <T> * p = front; //初始化工作指针
//若不是在第一个位置删除,得到第i-1个元素的地址
if (i != 1) p = Get(i-1);
if (!p || !p->next) throw"删除位置错误";
Node <T> * q = p->next;
p->next = q->next;
T x = q->data;
delete q;
return x;
}
举一反三
已知L是带头结点的单链表,且P结点既不是首结点,也不是尾结点,试从下列语句中选择合适的序列。
1 删除P结点的直接后继结点
Q=P->next;
P->next=P->next->next;
delete Q;
2 删除P结点的直接前驱结点
Q=P;
P=L;
while(P->next->next!=Q)
P=P->next;
Q=P->next;
P->next=Q->next;
delete Q;
3 删除P结点
Q=P;
P=L;
while(P->next!=Q)
P=P->next;
P->next=P->next->next;
delete Q;
4 删除首结点
P=L;
Q=P->next;
P->next=P->next->next;
delete Q;
5 删除尾结点
while(P->next->next=NULL)
P=P->next;
Q=P->next;
P->next=NULL;
delete Q;
实例
//LinkList.h
template <class T>
struct Node
{
T data;
Node<T> *next;
};
template <class T>
class LinkList
{
public:
LinkList(); //无参构造函数
LinkList(T a[], int n); //有参构造函数,使用含有n个元素的数组a初始化单链表
~LinkList(); //析构函数
void PrintList(); //按次序遍历线性表中的各个数据元素
int GetLength(); //获取线性表的长度
Node <T> * Get(int i); //获取线性表第i个位置上的元素结点地址
int Locate(T x); //查找线性表中值为x的元素,找到后返回其位置
void Insert(T x, int i);//在线性表的第i个位置上插入值为x的新元素
T Delete(int i); //删除线性表第i个元素,并将该元素返回
private:
Node <T> * front; //头指针
};
template <class T>
LinkList<T>::LinkList()
{
front = new Node<T>;
front->next = NULL;
}
template <class T>
LinkList<T>::LinkList(T a[], int n)
{
front = new Node<T>;
Node<T> *r = front;//初始化头结点
for (int i = 0; i < n; i++)
{
Node<T> *s = new Node<T>;
s->data = a[i];
s->next = NULL;// 生成新结点
r->next = s;// 链接在尾结点后面
r = s;// 尾指针后移
}
}
template <class T>
LinkList<T>::~LinkList() //析构函数
{
Node<T> *p = NULL; //初始化工作指针p
while (front != NULL) //要释放的结点存在
{
p = front->next; //暂存要释放的结点
delete front; //释放结点
front = p; //移动工作指针
}
}
template <class T>
Node <T> *LinkList<T>::Get(int i)
{
Node<T> *p = front->next; //初始化工作指针
int j = 1; //初始化计数器
while (j != i&&p != NULL) //两个条件都满足,则继续循环
{
p = p->next; //工作指针后移
j++;
}
if ( p== NULL) throw "位置非法";
return p;
}
template <class T>
int LinkList<T>::Locate(T x)
{
Node <T> * p = front->next; //初始化工作指针
int j = 1;
while (p)
{
if (p->data == x) return j;//找到被查元素,返回位置(注意“==”一定情况下要运算符重载)
p = p->next;
j++;
}
return -1; //若找不到,返回错误标识-1
}
//在线性表的第i个位置上插入值为x的新元素
template <class T>
void LinkList<T>::Insert(T x, int i)
{
Node <T> * p = front; //初始化工作指针
//若不是在第一个位置插入,得到第i-1个元素的地址。
if (i != 1) p = Get(i-1);
if (p != NULL)
{
Node <T> * s = new Node <T>;//建立新结点
s->data = x;
s->next = p->next;
p->next = s; //将新结点插入到p所指结点的后面
}
else throw "插入位置错误";
}
template <class T>
T LinkList<T>::Delete(int i)
{
Node <T> * p = front; //初始化工作指针
//若不是在第一个位置删除,得到第i-1个元素的地址
if (i != 1) p = Get(i - 1);
if (!p || !p->next) throw "删除位置错误";
Node <T> * q = p->next;
p->next = q->next;
T x = q->data;
delete q;
return x;
}
template <class T>
void LinkList<T>::PrintList()
{
Node<T>*p = front->next;
cout << "按序号依次遍历线性表中的各个数据元素:" << endl;
while (p)
{
cout <<p->data;
p = p->next;
}
cout << endl;
};
template <class T>
int LinkList<T>::GetLength()
{
Node<T> *p = front; int i = 0;
while (p->next != NULL)
{
p = p->next;
i++;
}
return i;
};
//test.cpp
#include <iostream>
using namespace std;
#include "LinkList.h"
int main()
{
int a[7] = { 1,2,3,4,5,6,7 };
LinkList <int> list(a, 7);
list.PrintList();
list.Insert(0, 1);
list.PrintList();
int x = list.Delete(1);
cout << "删除元素:" << x << endl;
list.PrintList();
int p = list.Locate(4);
cout << "元素4的位置:" << p << endl;
}
循环链表
单循环链表:
定义:将单链表尾结点指针指向头结点
单循环空链表:
非空单循环链表
//构造函数
template <class T>
CLinkList<T>::CLinkList()
{
rear = new Node <T>;
rear->next = rear;
}
单循环链表与单链表相同点:
(1)存储结构基本相同,仅使用尾指针及其指针指向头结点
(2)基本操作实现类似
单循环链表与单链表不同点:
(1)头指针:
单链表:使用front标识头指针,
单循环链表:使用rear->next标识头指针;
(2)判断尾结点的方法:
单链表:p->next== NULL,
单循环链表:p==rear。
双链表
每个结点有两个指针域,指向前驱和后继
结点结构:
template <class T>
struct DNode
{
T data;
DNode <T> * prior;
DNode <T> * next;
};