线性表
数据元素之间的关系是线性的。
1.线性表的定义
线性表中的数据元素可以是各种各样的,但是同一个线性表中的数据元素必定具有相同的属性,即属于同一个数据类型,而且相邻的两个元素之间存在着序偶关系。
若将线性表记为:
(a1,a2,a3,a4,a5....an)
(1).存在着唯一一个被称为“第一个”的数据元素
(2).存在着唯一一个被称为“最后一个”的数据元素
(3).除了第一个以外,集合中的每一个元素均有且仅有一个前驱元素(上一个)
(4).除了最后一个以外,集合中的每一个元素均有且仅有一个后继元素(下一个)
链表
在介绍链表之前首先说一下数组,因为链表跟数组的功能类似,都是用来存储数据的,但是数组却要提前分配大小,如果太大,则造成了内存资源的浪费,而太小了又会造成数据的丢失,那有没有一种存储结构是按需分配,即输入多少数据就相应的分配多少空间来存储它们,链表便完全解决了这种数据跟空间不匹配的问题。
数组和链表都属于线性表的实现:
要存储线性表,必须要存储两类东西:
"数据" “逻辑关系(结构)”
在存储线性表的时候,有两种方式:
(1).顺序结构
使用存储单元的物理关系去描述他们的逻辑关系,逻辑关系在前面的元素,物理关系也在前面
指的是使用一组地址连续的存储单元依次存储线性表中的每一个数据元素---->"数组"
(1 3 5 7 9 2 4 6 8 10)
按照升序存储上面的元素
int a[10];
a[0] 1
a[1] 2
a[2] 3
....
a[9] 10
各个元素的物理结构的先后顺序,就可以表示它们的“逻辑关系”
顺序结构优点:
简单,查找方便(利用下标遍历)
缺点:
删除元素和插入元素不方便,要挪动太多的元素
有人就提出,逻辑关系一定需要物理关系去描述吗?
不需要,每一个元素在物理上的哪一个位置,并不重要,只需要能够正确的描述数据元素之间的逻辑关系即可。
存储一个数据元素的时候,再额外的开辟一个空间,来保存当前元素的下一个元素或者上一个元素的地址
这样,就出现了下面的链表。
链表的概念
链表就是由一个或多个含有指针成员的结构体,通过其指针成员的地址指向,形成了一种逻辑上的链式数据结构。
由图我们可以看出链表是由一个个节点进行链接而成,而这些节点我们可以用结构体定义,并用malloc函数进行分配内存。
//结点结构体的定义
struct node{
int data; //存储每个结点的数据
struct node *next; //存储下一数据结点的地址
};
//创建一个结点
struct node *create_a_point(){
int a=5; //设要存如结点的数据为a
struct node *pnew; //创建一个新结点pnew
pnew=(struct node *)malloc(sizeof(*pnew)); //给新结点pnew分配内存
pnew->data=a; //a存入结点
pnew->next=NULL; //因为不知道有无下一结点,next暂定为NULL
return pnew;
}
以上只是简单定义了结点类型以及分配内存,并没有涉及到链接问题。
接下来存一下当时做的几个作业:
#include"linklist.h"
struct node *create_linklist(){ //实现构造构造一个链表
struct node *first=NULL; //存放链表第一个结点的地址
struct node *last=NULL; //存放链表最后一个结点的位置
struct node *p=NULL;
while(1){
Elemtype num;
scanf("%d",&num);
if(!num) break;
p=(struct node *)malloc(sizeof(*p));
p->data = num;
p->next = NULL;
if(!first) first = last = p; //如果没有结点,第一个和最后一个都是这个新加入的结点
else{ //尾插
last->next = p;
last = p;
}
}
return first; //返回第一个节点的地址
}
void print_list(struct node *list){ //实现顺序输出一个链表
struct node *p = list;
while(p){
printf("%d ",p->data);
p = p->next;
}
printf("\n");
}
void print_list1(struct node *list){ //实现顺序输出一个链表(不带空格)
struct node *p = list;
while(p){
printf("%d",p->data);
p = p->next;
}
printf("\n");
}
void print_reverse_list(struct node *list){ //实现逆序输出一个链表
struct node *p = list;
int sum = 0,s[11111]; //sum记录数量,s数组存放链表元素
while(p){
s[sum++] = p->data;
p = p->next;
}
for(int i = sum-1 ;i >= 0 ; i--) printf("%d ",s[i]);
printf("\n");
}
struct node *reverse_list(struct node *list){ //实现链表就地逆置
struct node *p = NULL; //用来操作反向链接
struct node *pr = list; //反向链接操作的前面那个结点的地址
struct node *pn = pr->next; //反向链接操作的后面那个结点的地址
while(pn){
p = pn; //p存放需要更改链接方向的结点地址
pn = pn->next; //更新保存下一个结点地址
p->next = pr; //反向链接前面那个结点
if(pr == list) pr->next = NULL; //如果是第一个节点,那么它的指向改为空
pr = p; //将前面那个被指向的结点更新为下一个需要被指向的结点
}
return pr;
}
struct node *create_list_by_num(char *num){ //实现链表逆序存储一个整数的各个位上的数字
struct node *p = NULL;
struct node *first = NULL; //存储第一个结点的地址
struct node *last = NULL; //存储最后一个结点的地址
for(int i = strlen(num)-1 ; i>=0 ; i--){ //因为逆序,所以从后往前依次转换存储
p = (struct node *)malloc(sizeof(*p));
p->data = num[i]-'0';
p->next = NULL;
if(!first) first = last = p;
else{
last->next = p;
last = p;
}
}
return first;
}
struct node *add_two_numbers(struct node *la,struct node *lb){
struct node *a = la;
struct node *b = lb;
struct node *p = NULL;
struct node *first = NULL;
struct node *last = NULL;
int vis=0,c; //vis用来标记进位,c用来存位数相加结果
while(a || b){ //只要a和b中还有结点,就继续算加法
if(!a) c = b->data + vis; //只有b的结点,就算b+vis
else if(!b) c = a->data + vis; //只有a的结点,就算a+vis
else c = a->data + b->data + vis; //都有,算a+b+vis
if(c>9){ //如果加完之后超过9,则进位标记为1,c减掉10
vis = 1;
c -= 10;
}
else vis = 0; //否则没有进位
p = (struct node *)malloc(sizeof(*p)); //将处理结果存入新的链表
p->data = c;
p->next =NULL;
if(!first) first = last = p;
else{
last->next = p;
last = p;
}
if(a) a = a->next; //处理完该位,向后移,if防止出现段错误
if(b) b = b->next;
}
if(vis){ //都处理完了,判断是否还有进位,有则把最后的进位存进去
p = (struct node *)malloc(sizeof(*p));
p->data = vis;
p->next =NULL;
last->next = p;
last = p;
}
first = reverse_list(first); //将倒置的结果正过来
return first;
}
老师所讲的定义结构体变量方式:
1,栈空间:变量名不能重复,过了生存期自动释放。
2,堆空间:malloc只需要保存地址就可以了,生存期随进程持续性。
其实我对相应的操作以及生存期表示没什么问题,但是对与栈或者堆的概念有些抽象,以下是我在网上找的详细定义(此处堆和栈并非数据结构中的堆和栈,而是内存分配中的堆和栈):
实际上链表的分类有三种:
单链表:
双链表:
循环双链表:
实际上单链表就是概念所讲的简单的链接,但并不怎么用,用的比较多的还是双链表以及循环双链表。
在图中我们可以看出其实双链表就是比单链表多了一个指向前一个结点的指针,使得整个链表的操作变得更加灵活,而循环双链表是在双链表的基础上将首尾结点进行链接,使之链接成环,在基本创建过程中大同小异,可以自己思考怎样实现。
在上述过程中我们提到了首尾结点,那什么是首尾节点呢?其实很简单,用一个结构体分别存储下链表第一个和最后一个结点的地址,注意:如果在首尾进行添加、删除或者替换操作时,记得改变该结构体中首尾地址的指向。
首结点(head):链表中唯一一个只指向别的结点,而不被其他结点指向的结点。首结点的地址就是整个链表的首地址,它可以代表整个链表。
尾结点(tail):链表中唯一一个只被别的结点指向,而不指向任何结点的结点。尾结点的next成员的值一般是NULL(单链表中)
//首尾结点存储结构体
struct list{
struct node *first; //记录链表起始结点地址
struct node *last; //记录链表末尾结点地址
int sum; //记录链表共有多少结点
};
所以,以下做个总结:
(1)只要知道首结点的地址,则其他结点的数据就都能访问
(2)保存新的数据,只需要再创建一个结点,然后赋值添加进链表即可。
链表的创建步骤:
(1)每需要保存一个数据,就创建一个结点(结构体)
(2)把数据写入到结点中去
(3)把结点加入到链表
创建过程:
(1)从无到有:第一个结点诞生,此时首结点和尾结点都是它本身
(2)从少到多(添加):
在后面添加(尾插法)
在前面添加(头插法)
尾插法: 新结点接在尾结点后面,即尾结点指向新的结点,然后尾结点被新结点替代。 特点:先链入的结点在前面,后链入的结点在后面。
头插法: 新结点接在首结点前面,即新的结点指向首结点,然后首结点被新结点替代。 特点:后链入的结点在前面,先链入的结点在后面。
最后提供一段链表的代码供大家参考:
#include<stdio.h>
#include<stdlib.h>
//结点结构体的定义
struct node{
int data; //存储每个结点的数据
struct node *next; //存储下一数据结点的地址
struct node *pre;
};
//首尾结点存储结构体
struct list{
struct node *first; //记录链表起始结点地址
struct node *last; //记录链表末尾结点地址
int sum; //记录链表共有多少结点
};
//创建一个链表
struct list *create_list(){
struct node *pnew=NULL; //指向新创建的结点
struct list *l=(struct list *)malloc(sizeof(*l)); //
创建头节点并初始化
l->first=NULL;
l->last=NULL;
l->sum=0;
while(1){
int num;
scanf("%d",&num);
if(!num) break; //设输入一段数字存入链表,并以输入0结束
pnew=(struct node *)malloc(sizeof(*pnew)); //
给新结点分配内存,此处是个双链表
pnew->data=num;
pnew->next=NULL;
pnew->pre=NULL;
if(l->first==NULL){ //
如果链表还没有结点,此时链表首尾结点都指向pnew
l->first=pnew;
l->last=pnew;
}
else{ //尾插法
l->last->next=pnew; //尾结点向后指向变为新结点
pnew->pre=l->last; //新结点向前指向变为尾结点
l->last=pnew; //尾结点更新为新结点
#if 0 //头插法
l->first->pre=pnew; //头节点向前指向变为新结点
pnew->next=l->first; //新结点向后指向变为原来链表的首结点
l->first=pnew; //首结点更新为新结点
#endif
}
l->sum++; //更新链表结点数
}
#if 0
if(l->sum){ //
这是创建循环双链表,其实就是比双链表多了一步首尾相连
l->first->pre=l->last; //
头节点向前指向原来为NULL,变为尾结点地址
l->last->next=l->first; //
尾结点向后指向原来为NULL,变为头节点地址
}
#endif
return l;
}
//print打印一个链表
void print(struct list *l){
struct node *p=l->first;
while(p){
printf("%d ",p->data);
p=p->next;
}
putchar('\n');
}
int main(){
struct list *l=create_list();
print(l);
return 0;
}
蒟蒻欢迎指正。