文章目录
线性表的顺序表示(顺序表)
顺序表的静态分配
InitList(SqList &L)
#include<stdio.h>
#define Maxsize 10 //定义最大长度
typedef struct {
int data[Maxsize];
int length;
}SqList;
void InitList(SqList &L){
L.length=0;
}
int main(){
SqList L;
InitList(L);
for (int i=0;i<L.length;i++){
printf("data[%d]=%d",i,L.data[i]);
}
return 0;
}
顺序表的动态分配
IncreaseSize(SeqList &L,int len)
#include<stdio.h>
#include<stdlib.h> //malloc,free的头文件
#define Initsize 10
typedef struct{
int *data;
int Maxsize,length;
}SeqList;
void InitList(SeqList &L){
L.data=(int *)malloc(Initsize*sizeof(int));
L.Maxsize=Initsize; //最大长度
L.length=0; //顺序表的当前长度
}
void IncreaseSize(SeqList &L,int len){ //增加 数组的长度
int *p=L.data; //p指向原来的空间
L.data=(int *)malloc((L.Maxsize+len)*sizeof(int)); //另开辟出一个全新的空间 存放原来的和新的
for(int i=0;i<L.length;i++){
L.data[i]=p[i]; //迁移数据
}
L.Maxsize=L. Maxsize+len;
free(p); //释放原来的内存空间
}
int main(){
SeqList L;
InitList(L);
IncreaseSize(L,5);
return 0;
}
顺序表的插入
ListInsert(SqList &L,int i,int e)
#include<stdio.h>
#define Maxsize 10
typedef struct{
int data[Maxsize];
int length;
}SqList;
void InitList(SqList &L){
L.length=0;
}
bool ListInsert(SqList &L,int i,int e){ //1.i的合法值[1,length+1] ,2.存满了也不能存
if (i<1||i>L.length+1) //判断i的范围是否有效
return false;
if (L.length>=Maxsize) //存储空间已满
return false;
for(int j=L.length;j>=i;j--){ //将第i个以及以后的元素后移; j=5 位序12345
L.data[j]=L.data[j-1]; // 下标 01234 j[5]空白 j[4]=位序5的数据
}
L.data[i-1]=e; //在位序i处放入数组下标[i-1]
L.length++;
return true;
}
int main(){
SqList L;
InitList(L);
printf("插入结果:%d\n",ListInsert(L,1,4));
printf("插入结果:%d\n",ListInsert(L,2,6));
printf("插入结果:%d\n",ListInsert(L,5,6));
return 0;
}
顺序表的删除
ListDelete(SqList &L,int i,int &e)
#include<stdio.h>
#define MaxSize 10
typedef struct{
int data[MaxSize];
int length;
}SqList;
void InitList(SqList &L){
L.length=0;
}
bool ListDelete(SqList &L,int i,int &e){
if (i<1||i>L.length)
return false;
e=L.data[i-1];
for (int j=i;j<L.length;j++){
L.data[j-1]=L.data[j];
}
L.length--;
return true;
}
int main(){
SqList L;
InitList(L);
int e=-1;
if(ListDelete(L,2,e))
printf("已删除第二个元素,删除元素值为%d\n",e);
else
printf("删除失败\n");
return 0;
}
顺序表的按值查找
LocateElem(SqList L,int e)
#include<stdio.h>
#define Maxsize 10 //定义最大长度
typedef struct {
int data[Maxsize];
int length;
}SqList;
void InitList(SqList &L){
L.length=0;
}
bool ListInsert(SqList &L,int i,int e){ //1.i的合法值[1,length+1] ,2.存满了也不能存
if (i<1||i>L.length+1) //判断i的范围是否有效
return false;
if (L.length>=Maxsize) //存储空间已满
return false;
for(int j=L.length;j>=i;j--){ //将第i个以及以后的元素后移; j=5 位序12345
L.data[j]=L.data[j-1]; // 下标 01234 j[5]空白 j[4]=位序5的数据
}
L.data[i-1]=e; //在位序i处放入数组下标[i-1]
L.length++;
return true;
}
int LocateElem(SqList L,int e){
for(int i=0;i<L.length;i++){
if (L.data[i]==e)
return i+1;
}
return 0; //退出循环 查找失败
}
int main(){
SqList L;
InitList(L);
printf("插入结果:%d\n",ListInsert(L,1,4));
printf("插入结果:%d\n",ListInsert(L,2,6));
printf("插入结果:%d\n",ListInsert(L,5,6));
for (int i=0;i<L.length;i++){
printf("data[%d]=%d\n",i,L.data[i]);
}
if(LocateElem(L,4)==0)
printf("查找失败\n");
else
printf("查找到的位序为:%d",LocateElem(L,4)) ;
return 0;
}
顺序表的按位查找
GetElem(SqList L,int i)
#include<stdio.h>
#define Maxsize 10 //定义最大长度
typedef struct {
int data[Maxsize];
int length;
}SqList;
void InitList(SqList &L){
L.length=0;
}
bool ListInsert(SqList &L,int i,int e){ //1.i的合法值[1,length+1] ,2.存满了也不能存
if (i<1||i>L.length+1) //判断i的范围是否有效
return false;
if (L.length>=Maxsize) //存储空间已满
return false;
for(int j=L.length;j>=i;j--){ //将第i个以及以后的元素后移; j=5 位序12345
L.data[j]=L.data[j-1]; // 下标 01234 j[5]空白 j[4]=位序5的数据
}
L.data[i-1]=e; //在位序i处放入数组下标[i-1]
L.length++;
return true;
}
int GetElem(SqList L,int i){
if (i<1||i>L.length)
return 0;
else
return L.data[i-1];
}
int main(){
SqList L;
InitList(L);
printf("插入结果:%d\n",ListInsert(L,1,4));
printf("插入结果:%d\n",ListInsert(L,2,6));
printf("插入结果:%d\n",ListInsert(L,5,6));
for (int i=0;i<L.length;i++){
printf("data[%d]=%d\n",i,L.data[i]);
}
printf("值为:%d",GetElem(L,2)) ;
return 0;
}
线性表的链式表示
带头结点的单链表的建立
struct Lnode next;
next指针用来指向链表的下一个节点,该节点同样为一个LNode结构体,因此next要声明为指向LNode结构体的指针struct LNode。
#include<stdio.h>
#include<stdlib.h>
typedef struct Lnode{
int data;
struct Lnode *next;
}Lnode,*LinkList;
bool InitList(LinkList &L){
L=(Lnode *)malloc(sizeof(Lnode)); //分配一个头节点
if(L==NULL) //内存不足分配失败
return false;
L->next=NULL; //头结点之后暂时还没有结点
return true;
}
bool Empty(LinkList L){
return (L->next==NULL);
}
void test(){
LinkList L;
InitList(L);
}
int main()
{
LinkList L; //声明一个指向单链表的指针
InitList(L);
test();
printf("%d",Empty(L));
}
不带头结点的单链表的建立
#include<stdio.h>
#include<stdlib.h>
typedef struct Lnode{ //定义单链表结点类型
int data; //每个结点存放一个数据元素
struct Lnode *next; //指针指向下一个节点
}Lnode,*LinkList; //LinkList强调是一个链表
bool InitList(LinkList &L){
L=NULL; //防止脏数据
return true;
}
bool Empty(LinkList L){
return (L==NULL)
}
void test(){
LinkList L;
InitList(L);
printf("%d",Empty(L));
}
int main(){
test();
}
头结点
个人理解头结点就是L本身不为空了,L!=NULL,而L->next仍为空
不带头结点的L本身就是空的,所以L=NULL.
带头结点的单链表的按位插入
如果i=5,找到a4,插到a4后面,
如果i=6,此时if (p==NULL) return false派上用场,不能插在NULL后面。
#include<stdio.h>
#include<stdlib.h>
typedef struct Lnode{
int data;
struct Lnode *next;
}Lnode,*LinkList;
bool InitList(LinkList &L){
L=(Lnode *)malloc(sizeof(Lnode)); //分配一个头节点
if(L==NULL) //内存不足分配失败
return false;
L->next=NULL; //头结点之后暂时还没有结点
return true;
}
bool ListInsert(LinkList &L,int i,int e){ //Elemtype e
if(i<1)
return false;
Lnode *p; //指针p指向当前扫描到的节点
int j=0; //当前p指向的第几个节点(头结点是0)
p=L; //L指向头结点(第0个节点)
while(p!=NULL&&j<i-1){
p=p->next;
j++;
}
if (p==NULL) //i值不合法,不能在空数后插入
return false;
Lnode *s=(Lnode *)malloc(sizeof(Lnode));
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
int main()
{
LinkList L; //声明一个指向单链表的指针
InitList(L);
printf("%d",ListInsert(L,1,2));
}
不带头结点的单链表的按位插入
注意此处与头节点相比 要加上if(i==1)模块,并且把j=0改成j=1;
#include<stdio.h>
#include<stdlib.h>
typedef struct Lnode{
int data;
struct Lnode *next;
}Lnode,*LinkList;
bool InitList(LinkList &L){
L=NULL; //防止脏数据
return true;
}
bool ListInsert(LinkList &L,int i,int e){
if(i<1) return false;
if(i==1){
Lnode *s=(Lnode *)malloc(sizeof(Lnode));
s->data=e;
s->next=L;
L=s; //头指针指向新节点
return true;
}
Lnode *p;
int j=1; //当前p指向的是第几个节点
p=L; //p指向第一个节点
while(p!=NULL&&j<i-1){
p=p->next;
j++;
}
if(p==NULL)
return false;
Lnode *s=(Lnode *)malloc(sizeof(Lnode));
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
int main(){
LinkList L;
InitList(L);
printf("%d",ListInsert(L,1,2));
}
指定节点的后插操作
后插操作:在p节点之后插入元素e
之前的按位操作(在第i个位置插入元素e)就是在找到第i-1个节点后执行后插操作。
链表的连接指针只能往后寻找,如果给定一个p节点,可知p节点之后的结点,而不知道p结点之前的结点
#include<stdio.h>
#include<stdlib.h>
typedef struct Lnode{
int data;
struct Lnode *next;
}Lnode,*LinkList;
bool InitList(LinkList &L){
L=(Lnode *)malloc(sizeof(Lnode)); //分配一个头节点
if(L==NULL) //内存不足分配失败
return false;
L->next=NULL; //头结点之后暂时还没有结点
return true;
}
bool InsertNextNode(Lnode *p,int e){
if (p==NULL)
return false;
Lnode *s=(Lnode *)malloc(sizeof(Lnode));
if(s==NULL)
return false; //分配内存失败
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
bool ListInsert(LinkList &L,int i,int e){ //Elemtype e
if(i<1)
return false;
Lnode *p; //指针p指向当前扫描到的节点
int j=0; //当前p指向的第几个节点(头结点是0)
p=L; //L指向头结点(第0个节点)
while(p!=NULL&&j<i-1){
p=p->next;
j++;
}
return InsertNextNode(p,e);
}
int main()
{
LinkList L; //声明一个指向单链表的指针
InitList(L);
printf("%d",ListInsert(L,1,2));
}
指定节点的前插操作
bool InsertPriorNode(LinkList L,LNode *p,Elemtype e)
在p结点之前插入元素e
第一种方法:传入头指针,循环查找p的前驱节点q,在对q进行后插
第二种方法:
申请一个新节点s作为p节点的后继节点,将p节点的数据复制到s结点中,将p节点中的数据替换为元素e。
时间复杂度为O(1)
bool InsertPriorNode(Lnode *p,int e){
if (p==NULL)
return false;
Lnode *s=(Lnode *)malloc(sizeof(Lnode));
if(s==NULL)
return false; //分配内存失败
s->next=p->next;
p->next=s;
s->data=p->data;
p->data=e;
return true;
}
王道书版本:
在p结点之前插入结点s
bool InsertPriorNode(Lnode *p,Lnode *s){
if (p==NULL||s==NULL)
return false;
s->next=p->next;
p->next=s;
int temp=p->data;
p->data=s->data;
s->data=temp;
return true;
}
按位序删除
(带头节点)
找到第i-1个结点,将第i-1个结点指向第i+1个结点,并free第i个节点。
最坏,平均时间复杂度为O(n)
最好时间复杂度为O(1)
typedef struct Lnode{
int data;
struct Lnode *next;
}Lnode,*LinkList;
bool ListDelete(LinkList&L,int i,int &e){
if(i<1) return false;
Lnode *p;
int j=0;
p=L;
if(p!=NULL&&j<i-1){
p=p->next;
j++;
}
if(p==NULL) return false;
if(p->next==NULL) return false; //防止第i-1个结点后已无结点
Lnode *q=p->next; //令q指向被删除节点
e= q->data;
p->next=q->next;
free(q);
return true;
}
指定节点的删除
bool DeleteNode(LNode *p)
删除节点p,指定一个指针q指向指针p的后继节点,把后继节点的数据传给p节点,令p->next=q->next,释放q节点 (q节点就是p的替罪羊)
q节点之后可能指向一个节点,也有可能是NULL
时间复杂度为O(1)
#include<stdio.h>
#include<stdlib.h>
typedef struct Lnode{
int data;
struct Lnode *next;
}Lnode,*LinkList;
bool DeleteNode(Lnode *p){
if (p==NULL) return false;
Lnode *q=p->next;
p->data=p->next->data;
p->next=q->next;
free(q);
return true;
}
int main(){
return 0;
}
如果p刚好是最后一个节点,只能传入头指针。
单链表的局限性:无法逆向检索
单链表的按位查找
返回第i个元素(带头结点)
此时i<0不是之前的i<1。
如果i值过大不合法,也会返回NULL
平均时间复杂度:O(n)
typedef struct Lnode{
int data;
struct Lnode *next;
}LNode,*LinkList;
LNode * GetELem(LinkList L,int i){
if(i<0)
return NULL;
LNode *p;
int j=0;
p=L;
while(p!=NULL&&j<i){
p=p->next;
j++;
}
return p;
}
王道书版本:
令p不是指向头结点而是第一个结点
LNode * GetELem(LinkList L,int i){
int j=1;
LNode *p=L->next;
if(i==0) return L;
if(i<0)
return NULL;
while(p!=NULL&&j<i){
p=p->next;
j++;
}
return p;
}
单链表的按值查找
从第一个结点找到数据域等于e的结点
找不到就返回NULL
时间复杂度为O(n)
typedef struct Lnode{
int data;
struct Lnode *next;
}LNode,*LinkList;
LNode * LocateELem(LinkList L,int e){
LNode *p=L->next;
while(p!=NULL&&p->data!=e){
p=p->next;
}
return p;
}
单链表的长度
时间复杂度为O(n)
typedef struct Lnode{
int data;
struct Lnode *next;
}LNode,*LinkList;
int Length(LinkList L){
int len=0;
LNode *p=L;
while(p!=NULL){
p=p->next;
len++;
}
return len;
}
单链表的建立(尾插法)
将数据元素存入链表步骤:
1.初始化一个单链表
2.每次取一个数据元素查到表尾/表头
舍弃方法:时间复杂度为O(n方) 太高了
设置变量length记录链表长度
while 循环{
每次取一个数据元素e;
ListInsert(L,length+1,e)插到尾部;
length++;
}
设置一个表尾指针,进行后插操作。
r指针是表尾指针,s指针用于开拓新的插入区域,r紧随s后,最后记得将尾结点置空。
typedef struct Lnode{
int data;
struct Lnode *next;
}LNode,*LinkList;
LinkList List_TailInsert(LinkList &L){
int x; //输入的数据
L= (LinkList)malloc(sizeof(LNode)); //初始化空表
LNode *s,*r=L; //r是表尾结点,s是开拓者
scanf("%d",&x);
while(x!=9999){
s=(LNode *)malloc(sizeof(LNode));
s->data=x;
r->next=s;
r=s; //r指向新的表尾结点
scanf("%d",&x);
}
r->next=NULL; //尾结点置空
return L;
}
单链表的建立(头插法)
对头结点执行后插操作
使用头插法输入,先输入的数据在链表的尾部。
typedef struct Lnode{
int data;
struct Lnode *next;
}LNode,*LinkList;
LinkList List_HeadInsert(LinkList &L){
int x; //输入的数据
L= (LinkList)malloc(sizeof(LNode));
L->next=NULL; //防止指向脏数据
scanf("%d",&x);
while(x!=9999){
s=(LNode *)malloc(sizeof(LNode));
s->data=x;
s->next=L->next;
L->next=s; //r指向新的表尾结点
scanf("%d",&x);
}
return L;
}
应用:链表的逆置
算法思想:逆置链表初始为空,表中节点从原链表中依次“删除”,再逐个插入逆置链表的表头(即“头插”到逆置链表中),使它成为逆置链表的“新”的第一个结点,如此循环,直至原链表为空。
接下来,进行图解:
刚开始是这样
’循环前的操作
此时L->next=NULL,为一个独立空链表,后面的四个为另一个新链表
进入循环,分别用q和p记录第一个和第二个节点
q->next=L->next; L->next=q; 此为头插法的应用
进入第二轮循环,这是发生重大变化的关键时期
即
直到链表为空
LNode *Inverse(LNode *L)
{
LNode *p, *q;
p = L->next;
L->next = NULL;
while (p != NULL)
{
q = p;
p = p->next;
q->next = L->next;
L->next = q;
}
return L;
}
双链表的初始化
单链表无法逆向检索,双链表可进可退
在单链表的基础上在增加一个指针域*prior(前驱指针)
#include<stdio.h>
#include<stdlib.h>
typedef struct DNode{
int data;
struct DNode *prior,*next;//前驱指针和后驱指针
}DNode,*DLinkList;
bool InitDLinkList(DLinkList &L) {
L=(DNode *)malloc(sizeof(DNode));
if(L==NULL) return false; //内存不足,分配失败
L->prior=NULL; //头结点的prior永远指向NULL
L->next=NULL; //头结点之后暂时还没有结点
return true;
}
bool Empty(DLinkList L){
if(L->next==NULL)
return true;
else
return false;
}
int main(){
DLinkList L;
InitDLinkList(L);
}
双链表的插入
在p结点之后插入s结点
将s结点的next指针指向p节点的下一个节点
将p节点的后继结点的前向指针指向s结点(如果没有后继节点(即NULL)就不用修改后继节点的前向指针)
把s结点的前向指针指向p结点
把p结点的后向指针指向s结点
考虑如果p结点是最后一个节点的情况
typedef struct DNode{
int data;
struct DNode *prior,*next;//前驱指针和后驱指针
}DNode,*DLinkList;
bool InsertNextDNode(DNode *p,DNode*s) {
if(p==NULL||s==NULL)
return false;
s->next=p->next; //始终s是主动地
if(p->next!=NULL){
p->next->prior=s;
}
s->prior=p;
p->next=s;
return true;
}
双链表的删除
删除p的后继节点q
typedef struct DNode{
int data;
struct DNode *prior,*next;//前驱指针和后驱指针
}DNode,*DLinkList;
bool DeleteNextDNode(DNode *p) {
if(p==NULL)
return false;
DNode *q=p->next; //q是p的后继节点
if(q==NULL) return false; //q没有后继节点
p->next=q->next;
//修改后继节点的前向指针时候要注意是不是NULL
if(q->next!=NULL)
q->next->prior=p;
free(q);
return true;
}
销毁整个链表:
循环释放各个数据结点,每一次都删除头结点的后继节点,然后释放头节点,头指针指向NULL
void DestroyList(DLinkList &L){
while(L->next!=NULL){
DeleteNextDNode(L);
}
free(L); //释放头结点
L=NULL; //头指针指向NULL
}
双链表的遍历
用于按值查找,按位查找
时间复杂度为O(n)
后向遍历:
while(p!=NULL){
p=p->next;
//对结点p作相应处理
...
}
前向遍历:
while(p!=NULL){
p=p->prior;
//对结点p作相应处理
...
}
前向遍历跳过头节点:
头结点的前向指针等于NULL,避免即可
while(p->prior!=NULL){
p=p->prior;
//对结点p作相应处理
...
}
循环单链表
表尾结点的next指针指向头结点
从一个结点出发可以找到其他任何一个结点
初始化:
typedef struct LNode{
int data;
struct LNode *next;
}LNode,*LinkList;
bool InitLIst(LinkList &L){
L=(LNode *)malloc(sizeof(LNode));
if(L==NULL)
return false;
L->next=L; //头结点指针指向L
return true;
}
判断是否为空:
如果是空表L->next指向自己L,如果不是,L->next指向下一个结点。
bool Empty(LinkList L){
return (L->next==L);
}
判断p结点是否为循环单链表的表尾结点:
bool isTail(LinkList L,Lnode *p){
return(p->next==L);
}
循环双链表
表头节点的prior指针指向表尾结点
表尾节点的next指针指向头结点
初始化:
bool InitDLinkList(DLinkList &L){
L=(DNode *)malloc(sizeof(DNode));
if(L==NULL)
return false;
L->prior=L;
L->next=L; //头结点指针指向L
return true;
}
判断是否为空:
bool Empty(LinkList L){
return (L->next==L);
}
判断p结点是否为循环单链表的表尾结点:
bool isTail(DLinkList L,Dnode *p){
return(p->next==L);
}
双链表的插入:
在p结点后插入s结点
bool InsertNextDNode(DNode *p,DNode*s) {
if(p==NULL||s==NULL)
return false;
s->next=p->next; //始终s是主动地
p->next->prior=s;
s->prior=p;
p->next=s;
return true;
}
双链表的删除:
bool DeleteNextDNode(DNode *p) {
if(p==NULL)
return false;
DNode *q=p->next; //q是p的后继节点
p->next=q->next;
q->next->prior=p;
free(q);
return true;
}
静态链表
容量固定不可变
分配一整片连续的内存空间,各个节点集中安置
0号节点充当头结点
游标为-1表达到达表尾
如每个数据元素4B,每个游标4B(每个结点共8B),
设起始地址为addr,则
e1的存放地址为addr+8*2
struct Node{
int data; //存储数据元素
int next; //下一个元素的数组下标
};
int main(){
//强调a是一个Node型数组
struct Node a[MaxSize]; //数组a作为静态链表
}
王道书版本:
typedef struct{
int data;
int next;
}SLinkList[MaxSize];
void test(){
SlinkList a; //强调a是一个静态链表
}
等价于
struct Node{
int data;
int next;
};
typedef struct Node SlinkList[MaxSize];//可用SLinkList定义一个长度为MaxSize的Node型数组
初始化静态链表
将a[0]的next设为-1
查找
从头结点出发挨个往后遍历结点
插入位序为i的结点
1.找到一个空的结点,存入数据元素(判断为空,可令next为某个特殊值,如-2)
2.从头结点出发找到位序为i-1的节点
3.修改新节点的next
4.修改i-1号节点的next
删除结点
1.从头结点出发找到前驱结点
2.修改前驱结点的游标
3.被删除节点next设为-2
顺序表versus链表
逻辑结构:都属于线性表,都是线性结构
存储结构:
基本操作:
创:
销毁:
增,删:
查找:
随机存取 顺序存取 随机存储 顺序存储
注意随机存取不等于随机存储
存取结构:分为随机存取和非随机存取(又称顺序存取)
1、随机存取就是直接存取,可以通过下标直接访问的那种数据结构,与存储位置无关,例如数组。
非随机存取就是顺序存取了,不能通过下标访问了,只能按照存储顺序存取,与存储位置有关,例如链表。
2、顺序存取就是存取第N个数据时,必须先访问前(N-1)个数据 (list),随机存取就是存取第N个数据时,不需要访问前(N-1)个数据,直接就可以对第N个数据操作 (array)。
存储结构:分为顺序存储和随机存储
1.顺序存储结构
在计算机中用一组地址连续的存储单元依次存储线性表的各个数据元素,称作线性表的顺序存储结构。
顺序存储结构是存储结构类型中的一种,该结构是把逻辑上相邻的节点存储在物理位置上相邻的存储单元中,结点之间的逻辑关系由存储单元的邻接关系来体现。顺序存储结构的主要优点是节省存储空间,因为分配给数据的存储单元全用存放结点的数据,结点之间的逻辑关系没有占用额外的存储空间。采用这种方法时,可实现对结点的随机存取,即每一个结点对应一个序号,由该序号可以直接计算出来结点的存储地址。但顺序存储方法的主要缺点是不便于修改,对结点的插入、删除运算时,可能要移动一系列的结点。
2、随机存储结构
在计算机中用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。它不要求逻辑上相邻的元素在物理位置上也相邻。因此它没有顺序存储结构所具有的弱点,但也同时失去了顺序表可随机存取的优点。