前言
C语言构造线性表的代码量就有点大了,所以在这一篇文章就只用C语言来做。其中面向对象语言编写起来的思想都差不多所以后面每章就选择一个语言来写。
这一章中我们来试着构造一下线性表,在上一篇文章中我们说了数据在计算机中有两种不同的存储方式一种是顺序存储结构和链式存储结构,所以这节课中我们要通过着两种不同结构和上节课构造的基结构来构造我们的线性表。关于线性表的定义我也不废话了,具体可以可能看看百度百科线性表
代码量有点多所以我也就不废话了。
顺序存储
先把完整的代码给放出来
#include<stdio.h>
#include<stdlib.h>
#define BaseStruct LinkList//标注BaseStruct现在是什么类型 比如说现在是列表 对列表进行操作
#define LinkSize 10
void initData();
short add();
void removeh();
typedef int BaseType;//定义baseStruct的基本类型
typedef struct node{
BaseType data;
struct node *next;
} Node;//链式存储需要用到这样的一个结构
typedef struct basestruct{
BaseType *header;//不管是什么存储结构都会有一个头,通过开头地址就能找到下一个元素
int p;//p
void (*initData)();//初始化数据
short (*add)();//插入数据
void (*removeh)();//删除值 remove有函数用了这个名字所以换成removeh
void (*printbs)();//打印bs
void (*insert)();
}*BaseStruct;
//开始构建存储
void initData(BaseStruct bs){
bs->p=0;
bs->header=(BaseType*)malloc(LinkSize*sizeof(BaseType));
//分配了10个空间 你可以用a[11]进行访问,产生了越界,不会报错,但是这个数据在内存中是不安全的,数据随时可能会丢失,内存被拿走
}
short add(BaseStruct bs,BaseType val){
int i=0;
if(bs->p<LinkSize){
bs->header[bs->p]=val;
//printf("%d",bs->header[bs->p]);
bs->p++;
return 1;
}else{
return 0;
}
}
//在指定位置插入一个值
void insert(BaseStruct bs,int addr,BaseType val){
int i=addr-1;
if(i<bs->p){
if(bs->p!=10){
bs->p++;
}
for(i=(bs->p-1);i>(addr-1);i--){
bs->header[i]=bs->header[i-1];
}
bs->header[addr-1]=val;
}
}
void removeh(BaseStruct bs,int addr){
//addr为删除元素的位置 void是类型不能返回
int i=addr-1;
if(i<bs->p){
if(addr==LinkSize){
bs->header[LinkSize-1]=0;
}else{
for(i;i<bs->p;i++){
bs->header[i]=bs->header[i+1];
}
bs->header[bs->p]=0;
}
bs->p--;
}
}
void printbs(BaseStruct bs){
int i=0;
while(i<bs->p){
printf("%d ",bs->header[i]);
i++;
}
}
void main(){
BaseStruct a=(BaseStruct)malloc(sizeof(BaseStruct));
a->initData=initData;
a->add=add;
a->removeh=removeh;
a->printbs=printbs;
a->insert=insert;
a->initData(a);
a->add(a,2);
a->add(a,3);
a->add(a,2);
a->add(a,3);
a->printbs(a);
printf("\n");
a->insert(a,4,4);
a->printbs(a);
getchar();
}
有几个点说一下
- 在BaseStruct结构体中,新增加了一个变量p。p是用来记录当前存放了几个元素的。
- 在initData当中,我们采用了malloc的方法分配区域(也可以通过定义数组,去分配内存)
- 重点说的还有两个函数removeh和insert
removeh
addr为我们需要删除的元素的位置,如我们想要删除第1个元素,就等同于删除bs->header[0]
所以我们先进行一个判断 ,判断我们要删除的元素是否大于我们目前线性表的大小,如果大于就什么都不进行操作,如果小于进行下一步判断,即判断要删除的元素是否等于最后一个元素,如果等于就直接将此元素设置为0(模拟删除),如果不等于就按照从前往后的顺序进行循环处理(与插入数据的顺序相反,防止数据的丢失),为什么要进行这一步判断呢?是因为我们malloc只分配了10个大小的连续空间给程序,如果不进行判断就可能将后面的分配给其他内存中的一小段数据给读出来,为了内存安全着想,还是做一下判断为好(尽管在这段程序中不会有什么影响)
insert
开始跟removeh一样的判断,我就不多说了。接着,我们要判断当前的线性表有没有达到最大的大小,因为线性表达到最大元素之后,我们插入一个元素线性表的大小是不会变化的。接着说一说这个循环吧,按照从后往前的顺序进行循环处理(与删除数据的顺序相反,这个顺序也是为了防止数据的丢失)
链式存储
#include<stdio.h>
#include<stdlib.h>
#define BaseStruct LinkList//标注BaseStruct现在是什么类型 比如说现在是列表 对列表进行操作
void initData();
void insert();
void removeh();
void add();
typedef int BaseType;//定义baseStruct的基本类型
typedef struct node{
BaseType data;
struct node *next;
} Node;//链式存储需要用到这样的一个结构
typedef struct basestruct{
Node *header;//不管是什么存储结构都会有一个头,通过开头地址就能找到下一个元素
void (*initData)();//初始化数据
void (*insert)();//插入数据
void (*removeh)();//删除值 remove有函数用了这个名字所以换成removeh
void (*printbs)();//打印bs
void (*add)();
}*BaseStruct;
void initData(BaseStruct bs){
bs->header=(Node*)malloc(sizeof(Node));
bs->header->next=NULL;
}
void add(BaseStruct bs,BaseType val){
Node *n=NULL;
Node *p=NULL;
n=(Node*)malloc(sizeof(Node));
n->data=val;
n->next=NULL;
p=bs->header;
while(p->next!=NULL){
p=p->next;
}
p->next=n;
}
void insert(BaseStruct bs,int addr,BaseType val){
//addr为要删除元素的位置
int i=0;
Node *pre,*n,*p;
p=bs->header;
while(p->next!=NULL){
pre=p;
//next=p->next;
p=p->next;
i++;
if(i==addr){
n=(Node*)malloc(sizeof(Node));
n->data=val;
pre->next=n;
n->next=p;
break;
}
}
}
void removeh(BaseStruct bs,int addr){
int i=0;
Node *p=NULL,*pre=NULL,*next=NULL;
p=bs->header;
while(p->next!=NULL){
pre=p;
p=p->next;
i++;
if(i==addr){
pre->next=p->next;
free(p);
break;
}
}
}
void printbs(BaseStruct bs){
Node *p=NULL;
p=bs->header->next;
while(p!=NULL){
printf("%d ",p->data);
p=p->next;
}
}
void main(){
BaseStruct a=(BaseStruct)malloc(sizeof(struct basestruct));
a->initData=initData;
a->insert=insert;
a->add=add;
a->removeh=removeh;
a->printbs=printbs;
a->insert=insert;
a->initData(a);
a->add(a,1);
a->add(a,2);
a->insert(a,1,3);
a->insert(a,2,5);
a->removeh(a,1);
a->printbs(a);
}
add函数
首先我们需要创建两个指针变量n和p,n指向新创建的节点的地址,而p是用来遍历整个链表用的。通过循环找到整个链表的最后一个节点,将新创建的节点接入。
insert函数
首先创建三个指针变量,pre指向当前操作节点的上一个节点,n指向我们新创建的节点的地址,同理p是用来对遍历整个链表的。接下来进入循环判断当前的位置是否为要操作的位置。如果是,则将链子断开,将pre指向的变量的next成员指向新创建的节点,再将新创建的节点的next成员指向下一个节点。
removeh函数
removeh和insert其实有一些相似,变量的含义跟之前一样,不同的是removeh在找到需要操作的位置后,将pre的指针指向当前操作节点的后一个节点,再利用free函数将当前操作节点清空。
对比
顺序存储
顺序存储要求内存中可用存储单元的地址必须是连续的。
优点:存储密度大(=1),易于查找和修改。
缺点:插入或删除元素时不方便;存储空间利用率低,预先分配内存可能造成存储空间浪费。
顺序表适宜于做查找这样的静态操作;
若线性表的长度变化不大,且其主要操作是查找,则采用顺序表;
链式存储
链式存储时,相邻数据元素可随意存放,但所占存储空间分两部分,一部分存放结点值,另一部分存放表示结点间关系的指针。
优点:插入或删除元素时很方便,使用灵活,存储空间利用率高。
缺点:存储密度小(<1),查找和修改需要遍历整个链表。
链表宜于做插入、删除这样的动态操作。
若线性表的长度变化较大,且其主要操作是插入、删除操作,则采用链表。