线性表
知识储备:C结构体 C指针
1.顺序存储结构
- 定义 :线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。
1.结构体
- 定义结构体
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20
typedef int ElemType;
typedef int Status;
//结构体
typedef struct SqList{
ElemType data[MAXSIZE];
int length;
};
SqList样式模型如图所示:
如图结构体类型SqList。结构体的第一个变量data[MAXSIZE]数组用来存放int类型数据。需要存储的数据通过数组的形式进行存放。第二个变量length为当前data数组存放数据的个数。
如:存储22,56,82,56。data[20]数组使用前4个存储空间,其他空间都没被使用。这里的length=4,MAXSIZE=20;
2.查找
线性顺序结构的查找很简单,直接使用data[i]就可以查找到数据。i为对应的下标。
3.插入
Status ListInsert(struct SqList *L,int i,ElemType e){
printf("insert 第%d位 %d\n",i,e);
int k;
if(L->length==MAXSIZE){
return ERROR;
}
if(i<1||i>L->length+1){
return ERROR;
}
if(i<=L->length){
for(k=L->length-1;k>=i-1;k--){
L->data[k+1] = L->data[k];
}
}
L->data[i-1]=e;
L->length++;
return OK;
}
1)代码分析:
方法中的第一个参数代表了结点的指针,因为length需要发生变化,所以形参需要使用指针类型;
方法的第二个参数int i是要插入的位置,第三个参数是要插入的数据。
if(L->length==MAXSIZE){
return ERROR;
}
当L->length==MAXSIZE时,说明SqList已经装满了数据,数组不能再添加数据,此时返回ERROR。
if(i<1||i>L->length+1){
return ERROR;
}
参数i是要插入的位置。这里的i从i开始,若i<1时,不满足要求返回ERROR。在顺序存储结构中data的数组中的数据只能挨各存放,不允许如下图所示存放。所以,当i>L->length+1时,会返回ERROR。
插入数据的主要逻辑:
if(i<=L->length){
for(k=L->length-1;k>=i-1;k--){
L->data[k+1] = L->data[k];
}
}
L->data[i-1]=e; // 1
L->length++;
return OK;
第一种情况:在数组末尾插入。直接进行赋值操作。然后length需要加1。如下图所示:
第二种情况:数组中间插入。如:在第二位添加,则后面的数据都要后移,最后在需要插入的位置插入数据,如下图所示。
4.删除
Status ListDelete(struct SqList *L,int i){
printf("delete 第%d位: %d\n",i,L->data[i-1]);
int k;
if(L->length==0){
return ERROR;
}
if(i<1||i>L->length){
return ERROR;
}
if(i<L->length){
for(k=i-1;k<=L->length-1;k++){
L->data[k] = L->data[k+1];
}
}
L->length=L->length-1;
return OK;
}
1)代码分析
- 临界条件
if(L->length==0){
return ERROR;
}
if(i<1||i>L->length){
return ERROR;
}
当L->length==0时,说明此时没有存储数据,返回ERROR。 当i<1时或者i>L->length时,说明删除数据不在标准范围内,返回ERROR。
- 删除逻辑
if(i<L->length){
for(k=i-1;k<=L->length-1;k++){
L->data[k] = L->data[k+1];
}
}
L->length=L->length-1;
- 存在两种情况,上面的代码将两种情况进行了合并
第一种删除末尾元素,如图:最后一位元素56被删除,只需要length置为0,length长度-1
第二种情况删除的元素不在末尾,如图删除第二个元素,后方的元素需要前移,然后执行置零操作,length减1。
2.链式存储结构
- 1.链式存储结构模型:以单链表为例
如图,链表是由多个Node构成。每个Node成为一个结点,多个结点一起构成链表结构。
通过传递地址的形式可以将该地址的数据进行修改,而如果使用整型的形式仅仅对传递的数据进行了复制。
1.结构体
typedef struct Node{
ElemType data;
struct Node* next;
}Node;
typedef struct Node *LinkList;
typedef struct Node *LinkList 这句代码为Node类型的指针创建了别名。
2.创建
创建链表结构有两种方式:头部创建和尾部创建
1)头部创建
void createListHead(LinkList *L,int n){
LinkList p;
int i;
int a[4]={22,56,82,56};
//创建头结点
*L = (LinkList)malloc(sizeof(Node));
(*L)->next=NULL;
for(i=0;i<n;i++){
//创建的新结点
p = (LinkList)malloc(sizeof(Node));
p->data=a[i];
//p->next获取到上一次创建的node的地址
p->next=(*L)->next;
(*L)->next=p;
}
printf("createListHead create finish!\n");
}
第四部分C语言拓展中有讲到,通常我们要对指针指向的数据进行修改时便使用指针作为函数的形参。因为L作为链表的头指针,在头插法中,因此L->next需要不断指向新的元素的地址才能保证L作为链表的头结点,所以方法的第一个参数使用了指针类型。
*L = (LinkList)malloc(sizeof(Node));
(*L)->next=NULL;
创建头指针,其实(*L)相当于指向LinkList类型的指针,这里创建一个头结点。在链表存储结构的线性表中,头结点的数据域一般不使用,指针域用来存放第一个结点的地址。
for(i=0;i<n;i++){
//创建的新结点
p = (LinkList)malloc(sizeof(Node));
p->data=a[i];
//p->next获取到上一次创建的node的地址
p->next=(*L)->next;
//获取到新创建的P的地址
(*L)->next=p;
}
上面是头部创建链表的主要逻辑,如下图所示:从右向左创建结点,P(*L)指向的第一个元素的地址。
2)尾部创建
void createListTail(LinkList *L,int n){
LinkList p,r;
int i;
int a[4]={22,56,82,56}
*L = malloc(sizeof(Node));
r=*L;
for(i=0;i<n;i++){
p=(LinkList)malloc(sizeof(Node));
p->data = a[i];
r->next = p;
r=p;
}
r->next=0;
}
- 分析代码:
*L = malloc(sizeof(Node));
r=*L;
对于尾插法,第一个元素的地址不发生变化,所以L获取到的第一个地址不能变化。因此这里的L只有用来指向第一个元素。因为使用的尾插法,所以末尾的地址需要使用r记录。
- 1.创建Node的主要逻辑
for(i=0;i<n;i++){
p=(LinkList)malloc(sizeof(Node));
p->data = a[i];
r->next = p;
r=p;
}
观察下图,每次只有最后的一个元素r发生变化,r将新创建的元素放置到它的后一位:r->next = p,然后再将末尾元素存储起来:r=p。
r->next=0;
末尾结点没有后面结点的地址,用NULL(000000)表示。
3.查找
Status getElem(LinkList L,int i,ElemType *e){
int j;
LinkList p;
p=L->next;
j=1;
//p等于0时退出程序,否则都为true
while(p&&j<i){
p=p->next;
++j;
}
if(!p||j>i){
return ERROR;
}
*e = p->data;
printf("i=%d data=%d",i,*e);
return OK;
}
1)代码分析
//当p=0时,链表遍历完时还没找到就会走下面的判断返回ERROR.
//通过j<i找到对应的p->next
while(p&&j<i){
p=p->next;
++j;
}
if(!p||j>i){
return ERROR;
}
当p=0时,链表遍历完时还没找到就会走下面的判断返回ERROR.通过j<i找到对应的p->next,若i<1时,返回ERROR。
*e = p->data;
最终通过赋值操作获取到数据值。
4.插入
Status insertList(LinkList *L,int i,ElemType e){
int j;
LinkList p,s;
//将头结点的地址
p=*L;
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->next=s;
return OK;
}
1)代码分析
插入操作分两步进行: 第一步:查找到第i-1位的地址:p 第二步:进行插入操作
s=(LinkList)malloc(sizeof(Node));
s->data=e;
s->next = p->next
p->next=s;
主要在后两句,如图:在第二个位置插入s,这里的说的next要指向以前的第二个元素的地址,s->next=p->next 另外第一个元素的next应当指向s的地址,p->next=s。
5.删除
- 代码
Status ListDelete(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;
p->next = q->next;
e=q->data;
free(q);
return OK;
}
-
上述代码同样分为两步
- 第一步:查找第i-1位元素的地址p(可以看本小结的查找详解)
- 第二步:改变第i位元素的前后关联因素
-
删除逻辑
q=p->next;
p->next = q->next;
e=q->data;
free(q);
如图:若删除第二个元素,则P->next=Q->next,然后将Q通过free(Q)释放就可以了。代码如上所示。
3.总结
- 分析:顺序存储结构和链式存储结构分别进行了插入操作、删除和查找操作。
- 1)查找
对于顺序存储结构查找简单直接通过下标就能找到数据,而链式存储结构则通过遍历才能找到相应的数据。因此使用顺序存储结构效率较高。
- 2)删除 插入
对于顺序存储结构找到相应的位置后插入和删除数据,其他数据也要进行移动,所以效率较低。而对于链式存储结构只需要找到位置插入和删除数据就可以,因此效率较高。
4.C语言拓展
指针作函数的形参,看下面一个例子:
#include<stdio.h>
void swap(int x,int y);
void swap_point(int *x,int *y);
int main(void){
int x=5;
int y=40;
swap(x,y);
printf("old x=%d y=%d",x,y);
swap_point(&x,&y);
printf(" new x=%d y=%d",x,y);
return 0;
}
void swap(int x,int y){
int temp;
temp=x;
x=y;
y=temp;
}
void swap_point(int *x,int *y){
int temp;
temp=*x;
*x=*y;
*y=temp;
}
输出结果: old x=5 y=40 new x=40 y=5
代码地址:代码