1.线性表的定义
相较于数组,线性表可以有效的利用存储空间,是一种由同一数据类型构成的有序序列的线性结构。对于线性表的实现,有利用数组连续存储空间的顺序存储方式,也有通过链表实现的存储方式。为了加深对线性表的理解,以下对线性表的操作都将使用c语言进行演示:
2.顺序表的各种操作:
2.1 定义
#include<stdio.h>
#include<stdlib.h>
#define max 105
//实现一个存放int类型数据的顺序表
typedef struct ArrayList
{
int *data;//指针模拟数组
int maxsize;//数组的最大容量
int length;//顺序表中元素的实际个数
}MyArray;
2.2 初始化
//初始化顺序表
MyArray initArray()
{
MyArray b;
//申请最大容量空间
b.data=(int*)malloc(sizeof(int)*max);
if(b.data==NULL)
{
printf("内存分配失败");
}
//顺序表最大容量
b.maxsize=max;
//顺序表实际容量
b.length=0;
return b;
}
2.3 插入(顺序插入与指定插入)
//顺序插入
void add(MyArray *a,int k)
{
if(a->length<a->maxsize)
{
a->data[a->length]=k;
a->length++;
}
else{
printf("顺序表已满,不能插入\n");
}
}
//制定位置i插入k
void insert(MyArray *a,int i,int k)
{
if(a->length<a->maxsize)
{
//空出i的位置
for(int j=a->length-1;j>=i;j--)
{
a->data[j+1]=a->data[j];
}
a->data[i]=k;
a->length++;
}
else{
printf("顺序表已满,不能插入\n");
}
}
2.4 查找
//查找某个数据的的位置
int find(MyArray *a,int k)
{
for(int i=0;i<a->length;i++)
{
if(a->data[i]==k)
{
return i;
}
}
return -1;//k不存在
}
2.5 删除
//删除数据k
void delet(MyArray *a,int k)
{
//查找k的位置
int i=find(a,k);
if(i==-1)
{
printf("被删除的数据不存在\n");
}
for(int j=i;j<a->length-1;j++)
{
a->data[j]=a->data[j+1];
}
a->length--;
}
2.5 修改
//将k改为x
void change(MyArray *a,int k,int x)
{
//查找k的位置
int i=find(a,k);
if(i==-1)
{
printf("被修改的数据不存在\n");
}
a->data[i]=x;
}
3.链式表的各种操作
链表相较于顺序表,它的存储空间并不固定,而是通过上一节点的指针域链接,因此再链表的节点结构体中,不仅需要存储数据,还需要有个指针域,存储下一节点的地址或者是上一节点的地址(双向链表)。
3.1 单链表
#include<stdio.h>
#include<stdlib.h>
typedef struct Node
{
int data;//节点数据
struct Node *next;//下一个数据所在节点地址
}Node,*LinkList;
//LinkList=Node*,增加可读性:LinkList指头指针,Node*节点的指针,两者其实没有本质区别
//c中main外调用函数必须申明
Node* findNode(LinkList L,int k);
//初始化带头节点的空链表
LinkList initLinkList()
{
Node* s=(Node*)malloc(sizeof(Node));
if(s==NULL)
{
printf("空间分配失败\n");
}
else
{
s->next=NULL;
}
}
//头插法
LinkList HeadInsert(LinkList L,int k)
{
//先申请新节点保存数据k
Node* s=(Node*)malloc(sizeof(Node));
if(s==NULL)
{
printf("空间分配失败\n");
return L;
}
else
{
s->data=k;
s->next=L->next;
L->next=s;
return L;
}
return L;
}
//尾插法
LinkList RearInsert(LinkList L,int k)
{
Node* p=L;//p先指向头结点
while(p->next!=NULL)
{
p=p->next;
}
Node* s=(Node*)malloc(sizeof(Node));
if(s==NULL)
{
printf("空间分配失败\n");
}
else
{
s->data=k;
s->next=NULL;
p->next=s;
}
return L;
}
//中间插入
LinkList mid_insert(LinkList L,int x,int k)
{
Node* p=findNode(L,x);
if(p==NULL)
{
printf("数据%d不存在,插入失败\n",x);
return L;
}
Node* s=(Node*)malloc(sizeof(Node));
if(s==NULL)
{
printf("空间分配失败\n");
}
else
{
s->data=k;
s->next=p->next;
p->next=s;
}
return L;
}
//删除k所在的节点位置
LinkList Delet(LinkList L,int k)
{
if(L->next==NULL)
{
printf("空链表,删除失败\n");
return L;
}
Node* pre=L;
Node* p=L->next;
while(p!=NULL&&p->data!=k)
{
pre=p;
p=p->next;
}
if(p==NULL)
{
printf("数据%d不存在,删除失败\n",k);
return L;
}
pre->next=p->next;
free(p);
p=NULL;//防止p成为野指针
return L;
}
//查找数据k所在节点地址
Node* findNode(LinkList L,int k)
{
Node* p=L->next;
while(p!=NULL&&p->data!=k)
{
p=p->next;
}
return p;
}
void printff(LinkList L)
{
Node* p=L->next;
while(p!=NULL)
{
printf("%d ",p->data);
p=p->next;
}
printf("\n");
}
int main()
{
LinkList L=NULL;
//初始化空链表
L=initLinkList();
//头插法插入
L=HeadInsert(L,5);
L=HeadInsert(L,8);
L=HeadInsert(L,6);
printff(L);
//尾插
L=RearInsert(L,9);
L=RearInsert(L,1);
printff(L);
//中间插入
L=mid_insert(L,5,7);
printff(L);
//删除
L=Delet(L,8);
printff(L);
return 0;
}
3.2 双向链表
#include <stdio.h>
#include <stdlib.h>
//带头结点的双链表的节点结构
typedef struct Node
{
int data;//节点数据
struct Node* next;//下一个数据所在节点地址
struct Node* pre;//上一个数据所在节点地址
}Node,*LinkList;
//LinkList=Node*,增加可读性:LinkList指头指针,Node*节点的指针,两者其实没有本质区别
LinkList find(LinkList L,int k);
//初始化带头结点的空链表
LinkList initlist()
{
Node* p=(Node*)malloc(sizeof(Node));
if(p==NULL)
{
printf("空间分配失败\n");
}
else
{
p->next=p->pre=NULL;
}
return p;
}
//头插法
LinkList headInsert(LinkList L, int k)
{
Node* s=(Node*)malloc(sizeof(Node));
if(s==NULL)
{
printf("空间分配失败\n");
return L;
}
s->data=k;
s->pre=L;
s->next=L->next;
L->next=s;
if(s->next!=NULL)//空节点
{
s->next->pre=s;
}
return L;
}
//尾插法
LinkList rearInsert(LinkList L,int k)
{
Node* s=(Node*)malloc(sizeof(Node));
if(s==NULL)
{
printf("空间分配失败\n");
return L;
}
s->data=k;
//找尾节点
Node* p=L;
while(p->next!=NULL)
{
p=p->next;
}
s->next=p->next;
s->pre=s;
p->next=s;
return L;
}
//中间插入,数据x后面插入数据k
LinkList midInsert(LinkList L,int x,int k)
{
Node* s=(Node*)malloc(sizeof(Node));
if(s==NULL)
{
printf("空间分配失败\n");
return L;
}
s->data=k;
//找x所在节点
Node* p=find(L,x);
if(p==NULL)
{
printf("数据%d不存在,插入失败\n",x);
return L;
}
s->pre=p;
s->next=p->next;
p->next=s;
if(s->next!=NULL)//空节点
{
s->next->pre=s;
}
return L;
}
//查找
Node* find(LinkList L,int k)
{
Node* p=L->next;
while(p!=NULL&&p->data!=k)
{
p=p->next;
}
return p;
}
//删除
LinkList delet(LinkList L,int k)
{
if(L->next==NULL)
{
printf("空链表,删除失败\n");
return L;
}
Node* p=L->next;
if(p==NULL)
{
printf("数据%d不存在,删除失败\n",k);
return L;
}
p->pre->next=p->next;
if(p->next!=NULL)
{
p->next->pre=p->pre;
}
//傻瓜式
// Node* pp=p->pre;
// Node* pn=p->next;
// pp->next=pn;
// if(pn!=NULL)
// {
// pn->pre=pp;
// }
free(p);
p=NULL;
return L;
}
void printff(LinkList L)
{
Node* p=L->next;
while(p!=NULL)
{
printf("%d ",p->data);
p=p->next;
}
printf("\n");
}
int main()
{
LinkList L=initlist();
L=headInsert(L,4);
L=headInsert(L,5);
L=headInsert(L,6);
printff(L);
L=rearInsert(L,7);
L=rearInsert(L,9);
printff(L);
L=midInsert(L,5,1);
L=midInsert(L,9,8);
printff(L);
L=delet(L,6);
L=delet(L,7);
printff(L);
return 0;
}
3.3 循环链表(单向与双向)
#include<stdio.h>
#include<stdlib.h>
typedef struct Node
{
int data;//节点数据
struct Node *next;//下一个数据所在节点地址
}Node,*LinkList;
Node* findNode(LinkList L,int k);
//初始化带头节点的空循环单链表
LinkList initLinkList()
{
Node* s=(Node*)malloc(sizeof(Node));
if(s==NULL)
{
printf("空间分配失败\n");
}
else
{
s->next=s;
}
return s;
}
//头插法
LinkList HeadInsert(LinkList L,int k)
{
//先申请新节点保存数据k
Node* s=(Node*)malloc(sizeof(Node));
if(s==NULL)
{
printf("空间分配失败\n");
return L;
}
else
{
s->data=k;
s->next=L->next;
L->next=s;
return L;
}
return L;
}
//尾插法
LinkList RearInsert(LinkList L,int k)
{
Node* p=L;//p先指向头结点
while(p->next!=L)
{
p=p->next;
}
Node* s=(Node*)malloc(sizeof(Node));
if(s==NULL)
{
printf("空间分配失败\n");
}
else
{
s->data=k;
s->next=L;
p->next=s;
}
return L;
}
LinkList mid_insert(LinkList L,int x,int k)
{
Node* p=findNode(L,x);
if(p==L)
{
printf("数据%d不存在,插入失败\n",x);
return L;
}
Node* s=(Node*)malloc(sizeof(Node));
if(s==NULL)
{
printf("空间分配失败\n");
}
else
{
s->data=k;
s->next=p->next;
p->next=s;
}
return L;
}
//删除k所在的节点位置
LinkList Delet(LinkList L,int k)
{
if(L->next==L)
{
printf("空链表,删除失败\n");
return L;
}
Node* pre=L;
Node* p=L->next;
while(p!=L&&p->data!=k)
{
pre=p;
p=p->next;
}
if(p==L)
{
printf("数据%d不存在,删除失败\n",k);
return L;
}
pre->next=p->next;
free(p);
p=NULL;//防止p成为野指针
return L;
}
//查找数据k所在节点地址
Node* findNode(LinkList L,int k)
{
Node* p=L->next;
while(p!=L&&p->data!=k)
{
p=p->next;
}
return p;
}
void printff(LinkList L)
{
Node* p=L->next;
while(p!=L)
{
printf("%d ",p->data);
p=p->next;
}
printf("\n");
}
int main()
{
LinkList L=NULL;
//初始化空链表
L=initLinkList();
//头插法插入
L=HeadInsert(L,5);
L=HeadInsert(L,8);
L=HeadInsert(L,6);
printff(L);
//尾插
L=RearInsert(L,9);
L=RearInsert(L,1);
printff(L);
//中间插入
L=mid_insert(L,5,7);
printff(L);
//删除
L=Delet(L,8);
printff(L);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
//带头结点的循环双链表的节点结构
typedef struct Node
{
int data;//节点数据
struct Node* next;//下一个数据所在节点地址
struct Node* pre;//上一个数据所在节点地址
}Node,*LinkList;
//LinkList=Node*,增加可读性:LinkList指头指针,Node*节点的指针,两者其实没有本质区别
LinkList find(LinkList L,int k);
//初始化带头结点的空链表
LinkList initlist()
{
Node* p=(Node*)malloc(sizeof(Node));
if(p==NULL)
{
printf("空间分配失败\n");
}
else
{
p->next=p->pre=p;
}
return p;
}
//头插法
LinkList headInsert(LinkList L, int k)
{
Node* s=(Node*)malloc(sizeof(Node));
if(s==NULL)
{
printf("空间分配失败\n");
return L;
}
s->data=k;
s->pre=L;
s->next=L->next;
L->next=s;
s->next->pre=s;
return L;
}
//尾插法
LinkList rearInsert(LinkList L,int k)
{
Node* s=(Node*)malloc(sizeof(Node));
if(s==NULL)
{
printf("空间分配失败\n");
return L;
}
s->data=k;
//找尾节点
Node* p=L;
while(p->next!=L)
{
p=p->next;
}
s->next=p->next;
s->pre=s;
p->next=s;
s->next->pre=s;
return L;
}
//中间插入,数据x后面插入数据k
LinkList midInsert(LinkList L,int x,int k)
{
Node* s=(Node*)malloc(sizeof(Node));
if(s==NULL)
{
printf("空间分配失败\n");
return L;
}
s->data=k;
//找x所在节点
Node* p=find(L,x);
if(p==L)
{
printf("数据%d不存在,插入失败\n",x);
return L;
}
s->pre=p;
s->next=p->next;
p->next=s;
s->next->pre=s;
return L;
}
//查找
Node* find(LinkList L,int k)
{
Node* p=L->next;
while(p!=L&&p->data!=k)
{
p=p->next;
}
return p;
}
//删除
LinkList delet(LinkList L,int k)
{
if(L->next==L)
{
printf("空链表,删除失败\n");
return L;
}
Node* p=L->next;
if(p==L)
{
printf("数据%d不存在,删除失败\n",k);
return L;
}
p->pre->next=p->next;
p->next->pre=p->pre;
free(p);
p=NULL;
return L;
}
void printff(LinkList L)
{
Node* p=L->next;
while(p!=L)
{
printf("%d ",p->data);
p=p->next;
}
printf("\n");
}
int main()
{
LinkList L=initlist();
L=headInsert(L,4);
L=headInsert(L,5);
L=headInsert(L,6);
printff(L);
L=rearInsert(L,7);
L=rearInsert(L,9);
printff(L);
L=midInsert(L,5,1);
L=midInsert(L,9,8);
printff(L);
L=delet(L,6);
L=delet(L,7);
printff(L);
return 0;
}
实际上作为最简单的数据结构,链表在操作中要遵守的原则就是不能断表,其他也没什么难点,复习时根据代码画画结构图,就能很清晰的明白线性表的相关原理与操作了。
4.两者的选择
简单的来说,顺序表操作比较简单,表长变化不大,适合查找某个元素,但没办法动态分配空间,不适合经常进行插入和删除的情况。而链表就适用插入和删除,缺点是存储密度低,查找时间复杂度相对较高。