整体框架:
余下见下一篇博客:线性表--循环链表、双向链表 https://blog.youkuaiyun.com/heda3/article/details/83097671
线性表的顺序存储结构
一段地址连续的存储单元依次存储的数据元素
确定3个要素:
一般操作,贯穿整个链表!框架!
初始化线性表 InitList(*L)
1、线性表清空 ClearList(*L)
2、将线性表的第i个位置的元素值返回给e GetElem(L,i,*e)
3、查找线性表中和给定数据元素e相等的元素序号 LocateElem(L,e)
4、在线性表中第i个位置插入元素e ListInsert(*L,i,e)
5、删除线性表中第i个位置的元素,并返回给e LitDelete(*L, i,*e)
6、返回线性表中的元素个数 ListLength(L)
顺序存储结构的基本操作:获得第i个位置的元素O(1)、插入O(n)、删除操作O(n)(即是数组的操作使用C++方式,理解思路很重要)
#define MAXSIZE 30 //宏定义 分配空间
typedef int ElemType
typedef struct
{
ElemType data[MAXSIZE];
int length;
}Sqlist;
#define OK 1
#define ERROR 0
#define TURE 1
#define FALSE 0
typedef int Status;
/*找到第i个元素并返回*/
Status GetElem(Sqlist L,int i, ElemType *e)
{
if(L.length==0||i<1||i>L.length)//i 表示的是第i个位置 因此最小为1 最大为长度
rerun ERROR; //这个很关键
*e=L.data[i-1];
rerun OK;
}
/*在第i个位置之前插入元素e*/
Status ListInsert(Sqlist *L,int i, ElemType e)//注意C语言里"."的左操作数为值,"->"的左操作数为指针
{
int k;
if(L->length==MAXSIZE)
rerun ERROR;
if(i<1||i>L->length)
rerun ERROR;
if(i<=L->length)//判断是否在表尾
{
for(k=L->length-1;k>=i-1;k--)
{
L->data[K+1]=L->data[k];
}//第i-1个元素位置空闲
}
L->data[i-1]=e;
L->length++;
return OK;
}
/*删除第i个位置的元素*/
Status ListDelete(Sqlist *L,int i, ElemType *e)
{
int k;
if(L->length==0)
return ERROR;
if(i<1||i>L->length)
return ERROR;
if(i<L->length)//判断是不是表尾
{
for(k=i;k<L->length;k++)
L->data[k-1]=L->data[k]
}
L->length--;
return OK;
}
链式存储结构
一组任意的存储单元存储线性表的数据元素
结点:包括数据域和指针域
多了头结点、以及最后一个结点指针指向为空
存储结构:
typedef struct Node
{
ElemType data;
strcut Node *next;// 改为 Node *next;
} node;
typedef struct Node *LinkList;//改为 Node *LinkList
typedef 用法 https://blog.youkuaiyun.com/wangqiulin123456/article/details/8284939
获取链表第i个数据
//输入 链表L 位置i 返回:e
Status GetElem(LinkList L,int i,ElemType *e)
{
int j;
LinkList p;
p=L->next;
j=1;
while(p&&j<i)//指针一直往后移动
{
p=p->next;
++j;
}
if(!p||j>i)
{
return ErROR;
}
*e=p->data;
retun ok;
}
链表的插入操作:
//在第i个位置之前插入新的数据元素e
Status ListInsert(LinkList *L,int i,ElemType e)
{
int j;
LinKList p,s;//s 为暂存结点
p=*L;
j=1;
while(p&&j<i)
{
p=p->next;
++j;
}
if(!p||j>i)
{
return ERROR;
}
s=(LinkList)malloc(sizeof(Node));//生成新的结点
s->data=e;
s->next=p->next;//将p结点指向的下一结点地址给s的指针域
p->next=s;//将s的地址赋值给p的指针域
return ok;
}
单链表的删除
//在第i个位置之前插入新的数据元素e
Status ListInsert(LinkList *L,int i,ElemType e)
{
int j;
LinKList p,q;//为要删除的结点
p=*L;
j=1;
while(p&&j<i)
{
p=p->next;
++j;
}
if(!p||j>i)
{
return ERROR;
}
q=p->next;//q为要删除结点的缓存变量 将指针赋值给它
p->next=q->next;//起到断开连接作用
*e=q->data;//数据赋值给指针e
free(q);
return ok;
}
对于插入和删除越频繁的操作,单链表具有优势!
链表的初始化创建
分为:头插法和 尾插法
头插法: 每次都让新结点在前面
//创建一个n个结点的链表L
void CreateListHead(LinkList *L,int n)
{
LinkList p;
int i;
srand(time(0));
*L=(LinList)malloc(sizeof(Node));
(*L)->next=NULL;//只有头结点
for(i=0;i<n;i++)
{
p=(LinkList)malloc(sizeof(Node));
p->data=rand()%100+1;
p->next=(*L)->next;
(*L)->next=p;//在表头插入
}
}
尾插法
定义一个结点的变量让它代表链表的尾结点
void CreateListTail(LinkList *L,int n)
{
LinkList p,r;
int i;
srand(time(0));
*L=(LinkList)malloc(sizeof(Node));
r=*L;
for(i=0;i<n;i++)
{
p=(Node*)malloc(sizeof(Node));
p->data=rand()%100+1;
r->next=p;
r=p;//重新定位最后的结点
}
r->next=NULL;
}
单链表的整表删除
Status ClearList(LinkList *L)
{
LinkList p,q;
p=(*L)->next;
while(p)//单链表的关键点判断链表结束标志为最后一个结点为空
{
q=p->next;
free(p);
p=q;
}
(*L)->next=NULL;//最后自剩下头结点,并指向为空
return ok;
}
单链表创建的:头插法 和尾插法实现在控制台上显示实现(使用codeblock)
#include <iostream>
#include "stdio.h"
#include "stdlib.h"
using namespace std;
typedef struct List
{
int data;
struct List *next; //指针域
}List;
void HeadCreatList (List *L) //头插法建立链表
{
List *s;
L->next=NULL; //先建立一个带头结点的单链表
for (int i=0;i<10;i++)
{
s=(struct List*)malloc(sizeof(struct List)); //生成新结点
s->data=i;
s->next=L->next; //将L指向的地址赋值给S;
L->next=s; //插入到表头
// printf ("%d ",L->data);
}
}
void TailCreatList(List *L) //尾插法建立链表
{
List *s,*r;
r=L; //r为指向尾部的结点
for (int i=0;i<10;i++)
{
s=(struct List*)malloc(sizeof(struct List)); //生成新结点
s->data=i;
r->next=s; //将表尾终端结点的指针指向新结点
r=s; //将当前的新结点定义为表尾的终端结点
}
r->next=NULL; //表示当前链表结束
}
void DisPlay(List *L)
{
List *p=L->next;
while(p!=NULL)
{
printf ("%d ",p->data);
p=p->next;
}
printf("\n");
}
int main ()
{
List *L1,*L2;
L1=(struct List*)malloc(sizeof(struct List));
L2=(struct List*)malloc(sizeof(struct List));
HeadCreatList(L1);
DisPlay(L1);
TailCreatList(L2);
DisPlay(L2);
}
对比链表和顺序存储结构的特点:
从存储分配方式、时间性能(对于查找、插入、删除)、空间性能
单链表判断结束标志是结点的指向是否为空;顺序存储的判断是依据长度;
后面将要讲到的静态链表大小是预先分配好的,但是有元素的个数需要判断第一个备用结点的指向。
循环链表的判断结束标志是指针的指向是否是头结点!
10-16及静态链表+循环链表+双向链表+例子实现从头到尾打印一个链表
静态链表:在其它的一些语言中例如fortan没有指针的操作,为此使用数组充当指针,
#define MAXSIZE 1000
typedef struct
{
ElemType data;
int cur;//游标
}StaticLinkList;
静态链表的初始化:
//初始化的过程
/*
1、定义一个空间
2、将每个游标连接
*/
Status InitList(StaticLinkList space)
{
int i;
for(i=0;i<MAXSIZE;i++)
{
space[i].cur=i+1;
}
sapce[MAXSIZE-1].cur=0;
return ok;
}
静态链表相比于单链表,结点的插入和删除需要自己实现一个malloc和free函数。
试着想一个问题?数组是连续若要删除一个结点,它是不能释放的,因此做法是把此结点清空并留作备用结点。
演示申请一个空闲空间,每一次都将第一个元素cur指向的空闲空间使用,再把其它空闲的空间地址给为第一个结点。
int Malloc_SLL(StaticLinList sapce)
{
int i=sapce[0].cur;
if(space[0].cur)
space[0].cur=space[i].cur;//申请完空闲空间后,为了下一次依然能继续分配,则将另一个空闲空间的位置给第一个位置的游标cur
return i;//返回的是申请得到的空闲空间
}
静态链表的插入
/*静态链表L的第i位置插入e*/
StatusListInsert(StaticLinList L,int i,ElemType e)
{
int j,k,l;//l为暂存的计数变量
k=MAX_SIZE-1;//k 指向最后一个值
if(i<1||i>ListLength(L)+1)
return ERROR;
j=Malloc_SSL(L);//获得空闲分量的下标 j 同时第0个元素的cur继续指向新的备用链表
if(j)
{
L[j].data=e;
for(l=1;l<=i-1;l++)//循环的次数取决于插入的位置
{
k=L[k].cur; //如果在乙和丁之间插入丙,丁位置为3.执行2次操作
}//得到k=2
//关键的两句替换
L[j].cur=L[k].cur;//把i之前的cur赋值给 新元素j的cur
L[k].cur=j;//同时将新元素的位置(也即是下标给i之前的cur)
return ok;
}
return ERROR;
}
k=2的理解:
最终的结果:
静态链表的删除:
由上面所实现的结果:
首先实现释放结点的函数
void Free_SSL(StaticLinkList space,int k)
{
space[k].cur=space[0].cur;//相当于把此变量当做备用的空闲链表
space[0].cur=k;
}
Status ListDelete(StaticLinkList L,int i)
{
int j,k;
if(i<1||i>ListLength(L))
return ERROR;
k=MAX_SIZE-1;
for(j=1;j<=i-1;j++)
{
k=L[k].cur;
}
j=L[k].cur;//将要删除元素的前一个位置的下一个指向的位置给j
L[k].cur=L[j].cur;//j所指向的下一个位置给要删除元素的前一个位置的cur
Free_SSL(L,j);
return ok;
}
这一句的理解很重要:
总结:对比单链表和静态链表的插入和删除都是类似的。
例如插入是:在A和B之间插入,则将A指向的地址给X的指向地址,然后将X的地址给A的指向地址。而且插入需要申请结点
删除是:ABC,需要删除B,则将A指向的地址(B)的地址(C)给现在的A将要指向的地址(C)。删除需要释放结点
单链表可以使用free 和mallo函数,但是静态链表需要自己写,此过程的插入操作是将备用空间的序号给要插入的结点,然后再连接,最后还原新的备用链表空间。此过程的删除操作是将要删除的结点充当新的备用空间。