线性结构
是指在数据元素的非空有限集合中,存在唯一的一个称为第一个的数据元素(头结点),存在唯一的一个称为最后一个的数据元素(末节点),除了第一个外,集合中每一个数据元素都只有一个直接前驱,除了最后一个外,集合中每一个数据元素都只有一个直接后继。
线性表
线性表是具有线性结构特点,最简单且常用的一种数据结构。
线性表是一个数据元素的有限序列,其元素可以是一个数、一个符号、也可以是多个数据项组成的复合形式。
具有多个数据项组成的线性表。
线性表表示与实现
- 顺序表
采用顺序表表示的线性表,其中逻辑位置相邻的数据元素将存放到存储器物理地址相邻的存储单元之中。
在线性表中,数据元素之间的相对位置,可以与数据元素的值有关,或者无关,当元素数据的位置和它的值相关时,称为有序线性表,表中的元素按照其值某种顺序排序;当表中的数据元素位置与它的值之间没有联系,称之为无序线性表。
顺序表在存储单元
由于顺序表在存储单元是连续存储,只要知道顺序表的一个首地址,可以根据 Loc(ai) = Loc(a0) +(i-1)乘每一个存储单元的字节数,在这里存储单元占一个字节。根据这个,我们知道顺序表可以根据首地址推出任意一个数据元素地址,然后就可以访问,可以看出顺序是一种随机存储的数据结构。
顺序表的实现
一维数组是实现顺序表的最为简便。
顺序表操作集合
顺序表的基本操作
#include <stdio.h>
#include <stdlib.h>
#define OK 1 //定义该常量表示操作成功
#define ERROR 0 //定义该常量表示操纵失败
#define INIT_SIZE 3
#define INCREASE 3
typedef struct Sqlist{
int *slist;
int length;//当前顺序表中元素
int listsize;//申请的顺序表内存空间是多少
} Sqlist;
//该方法用于初始化顺序表,初始化操作实际上,分配了预定长度
//为100内存大小的一维数组,但是数组长度为0。
int init(Sqlist *p){
int i;
p->slist = (int *)malloc(INIT_SIZE*sizeof(int));
// p->slist = malloc(sizeof(ElemType));这个是一个
//数据的内存大小,初始化顺序表内存大小是INIT_SIZE个,所以要乘,另外指向首地址
if(p->slist==NULL){
return ERROR;
}
p->length= 0;//这里需要注意的就是我们定义了初表的表的数据元素为0,
//那数据元素下标就是从1开始,而p->slist 指向的数组是从0开始。需
//要注意在表中插入的第i个数据,实际上是p->slist[i-1]
p->listsize = INIT_SIZE;//这时内存大小应该是100个int类型内存大小
}
int insert(Sqlist *p,int i,int e){
int k;
if(i<1||i>p->length+1){//这里我们把顺序表第一个位置为0
return ERROR;
}
//当数组长度超过申请存储空间,应该再申请。
if(p->length>p->listsize){
p->slist= (int *)realloc(p->slist,(p->listsize+INCREASE)*sizeof(int));
//注意第二个参数,是增加后的内存空间,不是增加了多少。
//返回的指针需要注意
//如果 当前连续内存块足够 realloc 的话,只是将p所指向的空间扩大,并返回p的指针地址。 这个时候 q 和 p 指向的地址是一样的。
//如果 当前连续内存块不够长度,再找一个足够长的地方,分配一块新的内存,q,并将 p指向的内容 copy到 q,返回 q。并将p所指向的内存空间删除。
if(p->slist==NULL){//申请存储空间有误
return ERROR ;
}
p->listsize += INCREASE;
}
for(k =p->length-1;k>=i-1;k--){
p->slist[k+1] = p->slist[k];
}
p->slist[i-1]= e;
p->length++;//添加一个元素,元素长度加1
return OK;
}
int delete1(Sqlist *p,int i){
int j;
if(i<1||i>p->length){
return ERROR;
}
for(j = i;j<p->length;j++){
p->slist[j-1] = p->slist[j];
}
p->length--;
return OK;
}
int find(Sqlist *p,int value){
int i;
for(i =0;i<p->length;i++){
if(p->slist[i-1]==value){
printf("寻找的位置 %d \n",i);
break;
return OK;
}else{
continue;
}
}
return ERROR;
}
int listlength(Sqlist *p){
return p->length;
}
int listempty(Sqlist *p){
if(p->length==0){
printf("为空\n ");
return OK;
}else{
return ERROR;
}
}
int showlist(Sqlist *p){
int i = 0;
for(i = 0;i<p->length;i++){
printf("%d \n",p->slist[i]);
}
}
int main()
{
int choice;
Sqlist *p1 = (Sqlist *)malloc(sizeof(Sqlist));
int value;
int position;
do{
printf("1:初始化顺序表 \n");
printf("2:顺序表插入 \n");
printf("3:顺序表删除 \n");
printf("4:顺序表长度 \n");
printf("5:顺序表是否为空 \n");
printf("6:顺序表遍历 \n");
printf("7:顺序表查找 \n");
scanf("%d",&choice);
switch(choice){
case 1:
init(p1);
break;
case 2:
printf("请输入插入的位置与值:");
scanf("%d %d:",&position,&value);
insert(p1,position,value);
break;
case 3:
printf("请输入删除的位置:");
scanf("%d:",&position);
delete1(p1,position);
break;
case 4:
{
int a = listlength(p1);
printf("长度 %d \n",a);
}
break;
case 5:
listempty(p1);
break;
case 6:
showlist(p1);
break;
case 7:
printf("请输入查找的值 \n");
scanf("%d",&value);
find(p1,value);
break;
}
}while(choice>0);
}
- 链表
链表的特点是采用一组任意的存储单元来存放线性表最后哦那个给的数据元素,这些存储单元可以是连续的,也可以是不连续的。在链表中,数据元素之间的逻辑关系并不依赖其对应的存储地址,而是通过设置专门用于数据元素之间逻辑关系的指针描述。每一个数据元素包含数据域和指针域,数据元素的这种存储方式,称之为结点。
基于线性的链表称之为单链表,在每一个结点只包含一个指针,用于指示该结点的直接后继结点,整个链表通过指针相连,最后一个结点因为没有后继结点,其指针为空。
链表操作代码:
#include <stdio.h>
#include <stdlib.h>
#define ERROR 0
#define OK 1
typedef struct Node{
int data;
struct Node *next;
} Node;
int initNode(Node *p){
if(p==NULL){
return ERROR;
}
p->next = NULL;//初始化的时候,头指针不指向任何一个结点
return OK;
}
int insertNode(Node *p,int i,int value){
int j = 0;
//这里设置j从0开始,头结点只有头指针没有数据,不算是第一个数据
while(p&&j<i-1){ //通过指针链来寻找需要的节点
p = p->next;
j++;
}
//如果要插入第3个位置,那么经过上述的指针移动p指向的节点就是第二个。
if(!p||j>i-1){
return ERROR;
}
Node *h = (Node *)malloc(sizeof(Node));
h->next = p->next;
p->next = h;
h->data = value;
return OK;
}
int deleteNode(int i,Node *p){
int j = 0;
while(p&&j<i-1){
p = p->next;
j++;
}
if(!p||j>i-1){
return ERROR;
}
Node *s = p->next;
p->next = s->next;
free(s);//释放该结点的存储空间
return OK;
}
int getNode(Node *p,int i){
int j = 0;
while(p&&j<i){
p = p->next;
j++;
}
if(!p||j>i){
return ERROR;
}
printf("查找的元素值:\n");
printf("%d",p->data);
}
int showNode(Node *p){
Node *s = p->next;//注意第一个节点没有数据,要跳过第一个节点
while(s!=NULL){
printf("%d \n",s->data);
s = s->next;
}
}
int main()
{
int choice;
Node *p;
int positon;
int value;
p = (Node *)malloc(sizeof(Node));
do{
printf("1 初始化链表 \n");
printf("2 链表的插入 \n");
printf("3 链表的删除 \n");
printf("4 链表的遍历 \n");
printf("5 链表的查找 \n");
printf("请输入您的选择 \n");
scanf("%d",&choice);
switch(choice){
case 1:
initNode(p);
break;
case 2:
printf("请输入要插入的位置和值");
scanf("%d %d",&positon,&value);
insertNode(p,positon,value);
break;
case 3:
printf("请输入要删除的位置");
scanf("%d",&positon);
deleteNode(positon,p);
break;
case 4:
showNode(p);
break;
case 5:
printf("请输入要查找的位置");
scanf("%d",&positon);
getNode(p,positon);
break;
}
}while(choice>0);
}
循环链表
循环链表的实现与示意图
该链表的表尾结点指向的为该链表的第一个结点,则整个线性链表构成一个闭合回路,这种头尾相连的线性链表称之为循环链表,该表具备的特点是,在链表中,顺着指针链可以找到表中的任意结点。
两个循环链表的合并
双向链表的实现
#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
typedef struct Dnode{
int data;
struct Dnode *next;//指向后的指针
struct Dnode *prior;//指向前的指针
}Dnode;
int initDnode(Dnode *p){
if(p==NULL){
return ERROR;
}
p->next = NULL;//空表的时候,前后的指针是指向NULL
p->prior = NULL;
}
int insertDnode(Dnode *p,int i,int value){
int j = 0;//注意头结点记住0开始
while(p&&j<i-1){//通过指针链来移动,找到要插入的前一个结点
p = p->next;
j++;
}
if(!p||j>i-1){
return ERROR;
}
Dnode *s =(Dnode *)malloc(sizeof(Dnode));
if(p->next!=NULL){
p->next->prior = s;
}
s->next = p->next;
p->next = s;
s->prior = p;
s->data = value;
return OK;
}
int deleteDnode(Dnode *p,int i){
int j = 0;
while(p&&j<i-1){
p = p->next;
j++;
}
if(!p||j>i-1){
return ERROR;
}
Dnode *s = p->next;
s->next->prior = p;
p->next = s->next;
free(s);
return OK;
}
int shownextNode(Dnode *p){
Dnode *s = p->next;//注意头结点没有数据
while(s!=NULL){
printf("%d \n",s->data);
s = s->next;
}
}
int showpriorNode(Dnode *p){
while(p->next){//需要先通过指针链找到最后一个结点
p = p->next;
}
while(p->prior){
printf("%d \n",p->data);
p = p->prior;
}
}
int main()
{
int choice;
int position;
int value;
Dnode *p = (Dnode *)malloc(sizeof(Dnode));
do{
printf("1:双向链表初始化 \n");
printf("2:双向链表插入 \n");
printf("3:双向链表删除 \n");
printf("4:双向链表向后遍历 \n");
printf("5:双向链表向前遍历 \n");
scanf("%d",&choice);
switch(choice){
case 1:
initDnode(p);
break;
case 2:
printf("请输入位置和值\n");
scanf("%d %d",&position,&value);
insertDnode(p,position,value);
break;
case 3:
printf("请输入删除位置\n");
scanf("%d",&position);
deleteDnode(p,position);
break;
case 4:
shownextNode(p);
break;
case 5:
showpriorNode(p);
break;
}
}while(choice>0);
}
双向链表的插入删除的示意图
经过上述线性表的基本操作,我们可以发现插入操作,都是从右往左来,删除操作都是从左往右。特别双向链表,如果指针操作次序有误,也是会带来问题。
单链表的置返
首先将原链表断开成两个部分,第一部分包含买结点和第一个结点:第二部分为剩余的其他结点。逆置过程可通过循环,将第二部分的每个结点逐个插人第一部分中头结点的后面,即可实现单链表的逆置。
int invertNode(Node *p){
Node *s;
Node *r;
//保证单链表反转条件:如果只有一个头结点反转也没意思
//如果除了头结点只有一个结点,反转也没意思,头结点不变,
//一个结点反转和本身有啥区别
if(p->next&&p->next->next){
s = p->next->next;//找到第二部分的第一个结点
p->next->next = NULL;//这个部分需要断开两部分
// printf("%d",s->data);
while(s){//通过遍历第二部分的节点,依次插入
r = s->next;
s->next = p->next;
p->next = s;
s = r;
}
}
return OK;
}