一、顺序表那么好,为什么还需要链表
顺序表的缺点:
•顺序表的除了表尾外其他位置插入和删除元素需要移动其他元素,效率低;平均时间复杂度为O(n)。
•顺序表需要连续的空间,当数据量比较大时,如果没有足够的连续空间,顺序表失效!!
为解决该缺点,设计了链表。链表的插入和删除不需要移动其他元素,插入删除效率高;但失去了随机存取的优势。
另:这是一种颠覆性创新,另起炉灶,不在顺序存储的框架内修修补补!!
单链表是最简单的链式结构,由此发散、引申,可以构造更复杂的结构,比如树、图。
基础不牢,地动山摇,必须把链表学好。
二、单链表的定义
•单链表是通过在当前结点中存放后继结点的地址将序列数据像“链”一样连接起来,所以称为链表。
•单链表的“单”指的是当前结点中只存放一个地址数据,即后继数据结点的地址。
三、单链表的结点形态
四、单链表的整体形态
在单链表中设立头结点,是为了方便在链表的头部插入和删除结点。
本节我们讲的单链表的头结点和数据结点是相同类型。
有时候为了方便,头结点中可以设一个指向链表头的指针、一个指向链表尾的指针和一个表示链表长度的整数。这时候头结点的类型就和链表结点的类型就不同了。
下面程序中用到的头文件以及符号常量等如下:
#include<iostream>
using namespace std;
#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status;
//Status 是函数返回值类型,其值是函数结果状态代码。
typedef int ElemType;
//ElemType 为可定义的数据类型,此设为int类型
五、链表结点的C++描述
typedef struct LNode {
ElemType data; //结点的数据域
struct LNode *next; //结点的指针域
} LNode, *LinkList; //LinkList为指向结构体LNode的指针类型
这样,head就是头指针,length记录链表中元素结点的个数,不包括头结点。
六、生成一个结点
LNode *CreateNode(ElemType e)
{//为元素e生成一个结点
LNode *t=new LNode;
if(t==0) return 0;
t->data=e;
t->next=0;
return t;
}
用new运算符,向操作系统申请node类型的空间。
若操作系统说,我也不富裕,拒绝了,则返回0,即空指针,否则返回新结点的地址。
七、初始化
开始时,生成一个头节点,在构造函数中实现。
Status InitList(LinkList &L)
{ //算法2.6 单链表的初始化
//构造一个空的单链表L
L = CreateNode(0);
if(L==0) return ERROR;
return OK;
}
head是一个变量,里面存的是地址,是头结点的地址,这个头结点没有名字。
八、在某个结点后面插入元素
void insert(LNode* p, LNode* q)
{//p是链表中某个结点的指针,q是一个新结点的指针
//将q插入到p的后面
q->next = p->next;
p->next = q;
}
void insert(LNode* p, ElemType x)
{//在指针p所指的结点后面插入x
LNode* q, * t;
t = CreateNode(x);
insert(p, t);
}
C++的函数重载,函数名相同,参数不同。
在结点p后面插入q结点,一定不能先断开p后面的指针,断开前,一定要有指针指向原来p后面的结点,
不然就会丢失结点。
九、在头结点后面插入元素
void push_head(LinkList &L, ElemType e)
{//在头结点后面插入数据
insert(L,e) ;
}
有了前面 insert 函数的铺垫, 在头部插入结点的操作就是如此简单。
十、在链表尾部插入结点
void push_back(LinkList &L, ElemType e)
{//在头结点后面插入数据
LNode *p=L;
while(p->next)
{
p=p->next;
}
insert(p,e) ;
}
先找到链表的尾部结点,用指针p指向它,然后再p后面插入数据。
十一、得到第i个结点的地址
LNode *Locate_i(LinkList &L,int i)
{//查找 第i个结点,用指针指向它,并返回该指针
if(i<0) return 0;//不合法
if(i==0)return L;//把头结点看做0号结点
LNode *p=L->next;
int j=1;
while(j<i && p)
{
p=p->next;
j++;
}
return p;
}
在单链表中找到第 i 个结点,是一个基本操作。
十二、在第i个结点前插入数据
Status ListInsert(LinkList &L, int i, ElemType e)
{ //算法2.9 单链表的插入
//在带头结点的单链表L中第i个位置插入值为e的新结点
LNode *p=Locate_i(L,i-1);//寻找第i-1个位置
if(p==0) return ERROR;//找不到插入位置
LNode *t=CreateNode(e);//为e生成结点
t->next=p->next;
p->next=t;
return OK;
}
要在第 i 个结点处插入数据,就是在第 i-1 个结点后插入数据,需要先找到第 i-1 个结点。
十三、删除第i个结点
Status ListDelete(LinkList &L, int i)
{ //算法2.9 单链表的删除
//在带头结点的单链表L中,删除第i个位置
LinkList p, q;
p=Locate_i(L,i-1);//找到第i-1个结点
if(p==0) return ERROR;
q = p->next; //临时保存被删结点的地址以备释放
if(q==0) return ERROR;
p->next = q->next; //改变删除结点前驱结点的指针域
delete q; //释放删除结点的空间
return OK;
}
要删除第 i 个结点,需要先找到第 i-1 个结点。
十四、在链表中查找
LNode *LocateElem(LinkList L, int e)
{ //算法2.8 按值查找
//在带头结点的单链表L中查找值为e的元素
LinkList p = L->next;
//顺链域向后扫描,直到p为空或p所指结点的数据域等于e
while (p)
{
if(p->data==e)
return p;//查找成功
p=p->next;
}
return 0; //查找失败
}
十五、释放单链表占用的空间
在析构函数中实现
void freeAll(LinkList &L)
{//释放所有结点空间
LNode* p;
while (L->next)
{
p = L->next;
L->next = p->next;
delete p;
}
delete L;
L = 0;
}
十六、完整代码
#include<iostream>
using namespace std;
#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status;
//Status 是函数返回值类型,其值是函数结果状态代码。
typedef int ElemType;
//ElemType 为可定义的数据类型,此设为int类型
typedef struct LNode {
ElemType data; //结点的数据域
struct LNode *next; //结点的指针域
} LNode, *LinkList; //LinkList为指向结构体LNode的指针类型
LNode *CreateNode(ElemType e)
{//为元素e生成一个结点
LNode *t=new LNode;
if(t==0) return 0;
t->data=e;
t->next=0;
return t;
}
Status InitList(LinkList &L)
{ //算法2.6 单链表的初始化
//构造一个空的单链表L
L = CreateNode(0);
if(L==0) return ERROR;
return OK;
}
void insert(LNode* p, LNode* q)
{//p是链表中某个结点的指针,q是一个新结点的指针
//将q插入到p的后面
q->next = p->next;
p->next = q;
}
void insert(LNode* p, ElemType x)
{//在指针p所指的结点后面插入x
LNode* q, * t;
t = CreateNode(x);
insert(p, t);
}
void push_head(LinkList &L, ElemType e)
{//在头结点后面插入数据
insert(L,e) ;
}
void push_back(LinkList &L, ElemType e)
{//在头结点后面插入数据
LNode *p=L;
while(p->next)
{
p=p->next;
}
insert(p,e) ;
}
LNode *Locate_i(LinkList &L,int i)
{//查找 第i个结点,用指针指向它,并返回该指针
if(i<0) return 0;//不合法
if(i==0)return L;//把头结点看做0号结点
LNode *p=L->next;
int j=1;
while(j<i && p)
{
p=p->next;
j++;
}
return p;
}
Status ListInsert(LinkList &L, int i, ElemType e)
{ //算法2.9 单链表的插入
//在带头结点的单链表L中第i个位置插入值为e的新结点
LNode *p=Locate_i(L,i-1);//寻找第i-1个位置
if(p==0) return ERROR;//找不到插入位置
LNode *t=CreateNode(e);//为e生成结点
t->next=p->next;
p->next=t;
return OK;
}
Status GetElem(LinkList L, int i, ElemType &e)
{ //算法2.7 单链表的取值
//在带头结点的单链表L中查找第i个元素
//用e返回L中第i个数据元素的值
LNode *p=Locate_i(L,i);
if(p==0) return ERROR;//i不合法
e = p->data; //取第i个结点的数据域
return OK;
}
LNode *LocateElem(LinkList L, int e)
{ //算法2.8 按值查找
//在带头结点的单链表L中查找值为e的元素
LinkList p = L->next;
//顺链域向后扫描,直到p为空或p所指结点的数据域等于e
while (p)
{
if(p->data==e)
return p;//查找成功
p=p->next;
}
return 0; //查找失败
}
Status ListDelete(LinkList &L, int i)
{ //算法2.9 单链表的删除
//在带头结点的单链表L中,删除第i个位置
LinkList p, q;
p=Locate_i(L,i-1);//找到第i-1个结点
if(p==0) return ERROR;
q = p->next; //临时保存被删结点的地址以备释放
if(q==0) return ERROR;
p->next = q->next; //改变删除结点前驱结点的指针域
delete q; //释放删除结点的空间
return OK;
}
void freeAll(LinkList &L)
{//释放所有结点空间
LNode* p;
while (L->next)
{
p = L->next;
L->next = p->next;
delete p;
}
delete L;
L = 0;
}
void print(LinkList &L)
{
if(L==0)
cout<<"链表没有头结点"<<endl;
LNode *p=L->next;
while(p)
{
cout<<p->data<<" ";
p=p->next;
}
cout<<"\n";
}
int main()
{
LNode *H;
InitList(H);
push_head(H,1);
print(H);
push_head(H,3);
print(H);
push_head(H,5);
print(H);
push_back(H,6);push_back(H,7);push_back(H,8);
print(H);
int x;
GetElem(H,2,x);
cout<<x<<endl;
LNode *p=LocateElem(H,3);
cout<<p->data<<endl;
print(H);
ListDelete(H,3);
print(H);
ListInsert(H,1,1);
print(H);
ListInsert(H,2,2);
print(H);
ListInsert(H,3,3);
print(H);
ListInsert(H,4,4);
print(H);
freeAll(H);
print(H);
return 0;
}