链式储存结构
结点在存储器中的位置时任意的,即逻辑上相邻的数据元素在物理上不一定相邻,所以链式又被称为非顺序映像或链式映像。
用一组物理位置任意的存储单元来存放线性表的数据元素。这组存储单元可以时连续的,也可以时不连续的,甚至时零散分布在内存的任意位置上。即链表中元素的逻辑次序和物理次序不一定相同。
单链表由头指针唯一确定,因此单链表可以用头指针的名字命名
- 结点:数据元素的储存映像。由数据域和指针域两部分组成。
- n个结点由指针链组成一个链表。
线性表的链式表现
- 结点只有一个指针域的链表,称为但来年表或线性来年表(就是本文的主角
结点由两个指针域的链表,称为双链表;首位相接的链表称为循环链表。以后再说。
头指针: 是指向链表中第一个结点的指针
首元结点: 是指链表中存储第一个数据元素a1的结点
头节点: 是在来年表的首元结点之前附设的一个结点(头结点可以储存链表长度或其他信息,方便链表操作。也可以没有头结点)
头结点
关于头结点,我们来看看头结点的好处:
-
便于首元结点的处理
首元结点的地址保存在头结点的指针域中,所以在链表的第一个位置上的操作和其他位置一致,无需特殊操作。(首元结点不再由头指针指向 -
便于空表和非空表的用以处理
无论链表是否为空表,头指针都是指向头结点的非空指针,因此空表和非空表处理统一。
而你知道头结点也有自己的数据域,其可以为空,也可以放线性表长度等附加信息,但此结点不计入链表长度值 另外无头结点时,头指针为空表示空表。有头结点时,当头结点的指针域为空时表示空表。[头指针指向头节点,即存储头街的的地址] 。
链表(链式储存)的特点
- 就是前面说的,结点在储存器中的位置是任意的,。即链表中元素的逻辑次序和物理次序不一定相同
- 访问时只能通过头指针进入链表,并通过每个结点的指针域依次炒向后顺序扫描其余结点(顺序存取法)。
单链表的定义和表示
一张图表示:
存储结构
定义结构体
//将结构类型重命名为node,*LinkList
typedef struct node{//声明结点和指向结点的指针的类型
ElemType data;//数据域
struct node *next;//指针域
}node,*LinkList;//LinkList是指向结构体node的指针类型,让书写更简单(node *L / LinkList L)
那么定义是这样的:
这样是自己定义链表头指针,当然你也可以在初始化是返回一个头指针(链表)
函数
Status InitList(LinkList &L/*node *L LinkList L/); bool ListEmpty(LinkList L); bool DestroyList(LinkList &L); bool ClearList(LinkList &L); int ListLength(LinkList L); Status GetElem(LinkList L,int i,ElemType &e); node *LocateElem_one(LinkList L,ElemType e); int LocateElem_two(LinkList L,ElemType e); Status ListInsert(LinkList &L,int i,ElemType e); Status ListDelete_one(LinkList &L,int i,ElemType &e); Status ListDelete_two(LinkList &L,ElemType x,ElemType &e); void CreateListHead(LinkList &L,int n); void CreateListRear(LinkList &L,int n); void ListTravel(LinkList L);
初始化链表(构造一个空的单链表
/*
* 1.生成新结点作为头结点,用头指针L指向头结点
* 2.将头结点的的指针域置空
*/
Status InitList(Linklist &L/*node *L*/){
L = (LinkList) malloc(sizeof(node));
//L = new node;___c++
if(L == NULL) { //判断是否有足够的内存空间
printf("申请内存空间失败\n");
return false;
}
L->next = NULL;
return true;
}
或者将链表作为返回值
LinkList InitList() {
LinkList L;
L = (LinkList)malloc(sizeof(Node)); //申请结点空间
if(L == NULL) { //判断是否有足够的内存空间
printf("申请内存空间失败\n");
exit(0);
}
L->next = NULL; //将next设置为NULL,初始长度为0的单链表
return L;
}
判断链表为空
//判空
bool ListEmpty(LinkList L){//若为空表,则返回1,否则返回0
if(L->next)
return 0;
else
return 1;
}
销毁链表
//链表的销毁(销毁后链表不存在,头指针头结点都不存在了
//从头指针开始,依次释放所有结点(头结点也被删除
bool DestroyList(LinkList &L){
LinkList p;//node *p
while(L){//L非空
p = L;
L = L->next;
free(p);
//delete p;
}
return true;
}
清空链表
//清空链表(链表仍存在,但是无元素,成为空链表
bool ClearList(LinkList &L){
LinkList p,q;//node *p,*q
//p:存放当前需要删除的结点 q:用来存放p的下一个结点
p = L->next;//保护头指针,P现在是首元结点
while(p){//非空,没到表尾
q = p->next;
free(p);//delete p
p = q;
}
L->next = NULL;
return true;
}
求单链表的表长
//求链表的表长
int ListLength(LinkList L){
LinkList p;//node *p
p = L->next;//p指向首元结点(第一个结点
int len = 0;
//遍历链表,统计结点数
while (p){//非空
len++;
p = p->next;
}
return len;
}
链表的取值
//取单链表中对饿第i个元素的内容
//从链表头指针开始,顺着链域next逐个结点往下搜索,直至搜索到第i个结点
Status GetElem(LinkList L,int i,ElemType &e) {
if(i < 1 && i > ListLength(L))
return false;
LinkList p = L->next;
int cnt = 1;
while (p && cnt < i){//向后扫描,直到p指向第i个元素或者p为空
p = p->next;
cnt++;
}
if(!p || cnt > i)
return false;
e = p->data;
return true;
}
链表的查找,分为返回地址和序号两种
//返回地址
node *LocateElem_one(LinkList L,ElemType e){
LinkList p = L->next;//指向首元结点
while (p && p->data != e)p = p->next;
//所有结点都看完了还没找到p为空
return p;//找到返回L中值为e的元素的地址,查找啊hi白返回NULL
}
int LocateElem_two(LinkList L,ElemType e){
LinkList p = L->next;
int cnt = 1;
while(p && p->data != e){
p = p->next;
cnt++;
}
if(p)//p不为空,表示找到了
return cnt;
else
return false;
}
插入结点
//插入
/*
* 1.首先找到Ai-1的储存位置p
* 2.生成一个数据域为e的新结点S
* 3.插入新的结点【1.新阶段的指针域指向Ai 2,结点Ai-1的指针域指向相信结点】
*/
//第i个元素前插入元素e
Status ListInsert(LinkList &L,int i,ElemType e){
LinkList p = L;//头结点
int cnt = 0;
//寻找第i-1个位置,p指向i-1
while(p && cnt < i - 1){
p = p->next;
cnt++;
}
if(!p || cnt > i - 1)
return false;//插入位置非法【i大于表长,i小于1
LinkList s = (LinkList) malloc(sizeof(node));//新的结点
//s = new node;
s->data = e;//存入数据
//插入
s->next = p->next;
p->next = s;
}
删除结点
首先是删除某个序号的结点
//删除结点
/*
* 1.首先找到Ai-1的储存位置p,保存要删除的a值
* 2.令p->next指向Ai+1
* 3.释放Ai的空间
* 4.e返回被删除的值a
*/
//^删除某个序号的
Status ListDelete_one(LinkList &L,int i,ElemType &e){
LinkList p = L;//头结点
int cnt = 0;
//寻找第i个元素,并令p指向其前驱(p指向i-1
while(p->next && cnt < i - 1){
p = p->next;
cnt++;
}
//删除位置不合法
if(!(p->next) || cnt > i - 1)
return false;
LinkList q = p->next;//存第i个结点
p->next = q->next;//Ai-1的指针域指向Ai+1
e = q->data;//保存删除的数据域
free(q);
//delete q;
return true;
}
然后还有删除某个具体数据域的结点
//^删除某个数据域的结点
Status ListDelete_two(LinkList &L,ElemType x,ElemType &e){
LinkList p,pre;//pre为前驱结点,p为查找的结点。
p = L->next;//首元结点
while(p && p->data != x){//查找值为x的元素
pre = p;
p = p->next;
}
pre->next = p->next;//删除操作,将其前驱next指向其后继。
e = p->data;//被删除的数据域保存
free(p);
}
建立单链表
这里都默认为经过初始化
头插法(注释掉的是返回头指针的洗发,尾插法一样
/*头插法
* 1.从一个空链表开始,重复读入数据
* 2.生成新结点,将读入的数据存放到数据域中
* 3.从最后一个结点开始,依次将个节点插入到链表的前端
*/
//传入一个头结点(经过初始化的链表),也可以自己new一个(未经过初始化)
void CreateListHead(LinkList &L,int n){//插入n个结点
//新的头结点
L= (LinkList) malloc(sizeof(node));//没经过初始化
// s = new node;
L->next = NULL;//头结点指针域置空
for(int i = 0; i < n; i++){
LinkList p = (LinkList) malloc(sizeof(node));
//p = new node;
cin >> p->data;//输入数据域
//scanf(&p->data);
//插入到表头
p->next = L->next;
L->next = p;
}
}
/*
LinkedList CreateLinkedListHead(int n)//头插法{
Node *L;
L = (Node *)malloc(sizeof(Node));
L->next = NULL;
printf("输入元素:");
for (int i = 0; i<n; i++)
{
Node *p; //要插入的结点
p = (Node *)malloc(sizeof(Node));
cin >> p->data;//输入数据域
p->next = L->next;
L->next = p;
}
return L;
}
*/
尾插法:
//尾插法
/*
* 1.从一个空表开始,将新结点逐个插入到链表的尾部,尾指针r指向来年表的尾结点
* 2.初始化时,r与L同指向头结点。没读入一个数据元素则申请一个新的结点,将新结点插入到为节点后,r指向新的结点
*/
void CreateListRear(LinkList &L,int n){
L= (LinkList) malloc(sizeof(node));//没经过初始化
L->next = NULL;
LinkList r = L;//尾指针,先指向头结点
for(int i = 0; i < n; i++){
LinkList p = (LinkList) malloc(sizeof(node));
//插入到表尾
cin >> p->data;
p->next = NULL;
r->next = p;
r = p;//r指向新的尾结点
}
}
/*
LinkedList CreateLinkedListTail(int n)//尾插法
{
Node *L;
L = (Node *)malloc(sizeof(Node));
L->next = NULL;
Node *r;
r = L;
while (n--)
{
Node *p;
p = (Node *)malloc(sizeof(Node));
cin >> p->data;//输入数据域
r->next = p;
r = p;//之后的循环遇到r就是等同于p r->next就是p->next
}
r->next = NULL;
return L;
}
*/
链表操作: 有序表的合并:
void MergeLinkList(LinkList &La,LinkList &Lb,LinkList &Lc){
LinkList pa = La->next;
LinkList pb = Lb->next;//首元结点
LinkList pc = Lc = La;//将La的头结点作为Lc的头结点
while (pa && pb){//都不为空表
if(pa->data <= pb->data){
pc->next = pa;//此时La的元素接到Lc后面
//后移
pc = pa;
pa = pa->next;
}
else{
pc->next = pb;//此时Lb的元素接到Lc后面
//后移
pc = pb;
pb = pb->next;
}
// if(pa)
// pc->next = pa;
// else
// pc->next = pb;
pc->next = pa ? pa : pb;//如果La空l。那么Lb未空,把Lb剩下的接到Lc中
free(Lb);
//Lc,La指向同一节点
}
}