简述
线性表
线性表是具有相同特性的数据元素组成的一个优有限序列。
线性表的特征:
- 线性表中有且只有一个开始结点(头结点),这个开始结点没有后继结点
- 线性表中有且只有一个末尾结点(尾结点),这个末尾结点没有后继结点
- 除去头结点和尾结点,其他结点都有一个前驱结点和一个后继结点
线性表在存储结构上有顺序存储和链式存储两种,不管哪种存储方式,结构均具有以下特征
- 均匀性:对于同一个线性表来说,数据元素必须具有相同的数据类型和长度
- 有序性:数据之间的位置是线性的,即存在唯一的“第一个”和“最后一个”数据元素
线性存储
原理
顺序存储:就是在存储器中分配一段连续的存储空间,逻辑上相邻的数据元素,其物理存储地址也是相邻的。
- 线性表的顺序存储结构是一种随机存取的结构,在高级语言中顺序存储使用数组来实现的
- 在顺序存储中,系统不需要为表元素之间的逻辑关系增加额外的存储空间,它可以根据给出的下表快速的计算出元素的存储地址。
- 在顺序表中插入或删除元素,效率会特别的低
操作
- 插入
- 删除
- 修改
- 查找(顺序查找,二分查找)
实现
首先定义数组的长度
#define MAX 20
定义线性表结构
typedef struct {
int data[MAX];//用数组存储数据元素,最大容量MAX
int len = 0;//线性表长度
}seqlist;
初始化
void init(seqlist *list){
int scan;
printf("请输入要录入的个数(0~20)");
scanf("%d",&scan);
if (scan <=MAX && scan >=0)
{
for (int i = 0; i < scan; i++
scanf("%d",&list->data[i]);
list->len = scan;
}
else
printf("超出线性表长度\n");
}
查看线性表元素
void look(seqlist *list){
for (int i = 0; i < list->len; i++)
{
printf("%d ",list->data[i]);
}
}
插入数据
//插入元素(x:插入的位置。y:插入的数字)
void insertData(seqlist * list,int x,int y){
if(list -> len == MAX){
printf("error:线性表已满");
}
else if(x <=1 && x >= list -> len+1){
printf("error:线性表插入的数据要与上一个连续");
}
else{
for(int i = list -> len - 1;i >= x-1;i--)
list -> data[i+1] = list -> data[i];
list -> data[x-1] = y;
list -> len++;//每插入一个元素线性表长度加一
}
}
删除元素
//删除指定位置元素
void deleteData(seqlist *list,int x){
if(list->len == 0){
printf("error:线性表为空");
}
else if(x <= 1 && x >= list->len){
printf("error:找不到该位置元素");
}
else{
for(int i = x -1;i < list->len;i++){
list->data[i] = list->data[i+1];
}
list->len--;
}
}
修改数据
//直接覆盖原来位置上的数据
//修改表中数据(x:要修改的位置,y:修改后的数字)
void reData(seqlist *list,int x,int y){
if(x >=1 && x <= list->len){
list ->data[x-1] = y;
}
else
printf("error:该位置为空或线性表中不存在该元素");
look(list);
}
顺序查找
//顺序查找(带哨兵):浪费一个空间。将需要查找的元素(哨兵)
//存入线性表下表为零的位置,从MAX开始遍历,如果线性表中没有该元素,返回的
//下表为0(即哨兵所在位置),否则返回非零值。
int shun(int a[],int n){
int i = MAX;
a[0] = n;
while (a[i] != n)
{
i--;
}
return i;
}
二分查找
首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。如果 begin>last这时候搜索区间为空,说明未找到该元素
int twoFen(int a[],int n,int begin,int last){
//mid中间数下表,begin最小数下标,last最大数下标:
int mid = (last + begin)/2;
if (a[mid] == n)
{
return mid;
}
if (begin >= last)
{
return -1;
}
else if (n < a[mid])
{
return twoFen(a,n,begin,mid-1);
}
else if(n > a[mid])
{
return twoFen(a,n,mid+1,last);
}
}
链式存储
原理
在链式存储中,结点之间的存储单元地址是不连续的。链式存储中每个结点都包含两个部分:存储元素本身的数据域和存储结点地址的指针域
一般在链表中会一个头结点来保存链表的信息,然后有一个指针指向下一个结点,下一个结点又指向它后面的一个结点,这样直到最后一个结点,它没有后继结点,就指向NULL。
当在链表中某个位置插入元素时,从空闲空间中分配一个存储单元,然后将两个结点之间的指针断开,上一个结点的指针指向新分配的存储单元,新分配的结点中指针指向下一个结点,不需要移动原来元素的位置。
同样,当删除链表中某个元素时,就断开它与前后两个结点的指针,然后将它的前后两个结点连接起来。
链表不能随机查找元素。(图:在链表中插入数据)
实现
创建链表结构(这里定义一个变量lens用来统计链表长度,也可以单独定义一个包含长度信息的Header类型的结构体,后面不再详细描述)
int lens = 0;//累计长度
typedef struct NodeList{
int data;//数据域
struct NodeList *next;//(指针域)指向下一个节点
}list;
//包含长度信息的Header类型结构体
struct Header
{
int length;//记录链表长度
struct Node* next;//指向第一个结点的指针
}
开辟一个链表头结点
//开辟头结点
list *initList(){
//malloc函数在头文件include<stdlib.h>中
//malloc:向系统申请分配指定size个字节的内存空间。
//返回类型是void*类型.void*表示未确定类型的指针。C,C++规定,
//void*类型可以强制转换为任何其它类型的指针。
list *h = (list*)malloc(sizeof(list));//头节点分配空间
h->next = NULL;//初始化,头指针
return h;//返回头节点地址
}
查看链表所有元素
从头结点(或开始结点)出发,通过工作指针的反复后移而将整个单链表“审视”一遍的方法称为扫描(或遍历)。
//遍历结点
void seeList(list *h){
list *p;//工作指针
p = h->next;
while (p != NULL)
{
printf("%d ",p->data);
p = p->next;//工作指针后移
}
printf("\n");
}
初始化(尾插法)
将待插入结点插在终端结点的后面。
//尾插法
void initTail(list *h){
list *ls,*r = h;//定义一个尾指针r
int n;
scanf("%d",&n);
for (int i = 0; i < n; i++)
{
ls = (list*)malloc(sizeof(list));//分配空间
scanf("%d",&ls->data);
ls->next = NULL;//新分配的结点next指向NULL(新结点插入后就变成了尾结点)
r->next = ls;//该结点的地址赋给上一个结点的next
r = ls;//该节点变成上一个结点,
lens++;
}
}
头插法创建单链表是将待插入结点插在头结点的后面(不再详细介绍)
插入元素
//插入元素
void insertData(list *h){
int loc,x;
list *p = h;
printf("请输入插入的位置和数据:\n");
scanf("%d%d",&loc,&x);
list *ins = (list*)malloc(sizeof(list));//分配空间
ins->data = x;
if(loc <= lens && loc >= 1){
for(int i = 1;i <loc;i++){
p = p->next;
}//找到插入的位置
ins->next = p->next;
p->next = ins;
lens++;
printf("单链表长度:%d\n",lens);
}
else
printf("插入位置错误!\n");
seeList(h);
}
删除指定元素
//删除指定元素
void removeLoc(list *h){
list *p = h;
int loc;
printf("请输入删除的位置:\n");
scanf("%d",&loc);
for (int i = 1; i < loc; i++)
{
p = p->next;
}
if (p != NULL)
{
list *q = p->next;
p->next = q->next;
lens--;//链表长度减一
free(q);//释放空间
}
else
printf("error\n");
seeList(h);
printf("单链表长度:%d\n",lens);
}
查找指定位置元素
//查找指定位置元素
void selectLoc(list *h){
list *p = h->next;
int loc;
printf("请输入要查找的位置:\n");
scanf("%d",&loc);
if (loc >= 1 && loc <= lens)
{
for (int i = 1; i < loc; i++)
{
p = p->next;
}
printf("第%d个位置的数据为:%d",loc,p->data);
}
else
printf("找不到该元素\n");
}
销毁链表
//销毁表
void Destroy(list *h){
list *p = h;
list *q;//保证链表未处理的部分不断开
while (p ->next != NULL)
{
q = p->next;
free(p);
p = q;
}
lens = 0;
h ->next = NULL;
}
----------其他操作不再详细叙述----------
循环链表
循环链表是首尾相接的一种链表,它的未结点的后继指针又指向链表的第一个结点。
对于循环链表,从表中任意一个结点出发,都能找到其他所有的结点。