线性表-链表 单向链表 循环链表 静态链表
文章目录
一、线性表基本概念
1.线性表的定义
线性表是具有相同数据结构类型的n(n>=0) 个数据元素的有限序列,其中n为表长,当n=0是线性表是个空表。
线性表示方式
:
L
=
(
a
1
,
a
2
,
3
,
.
.
.
.
.
.
,
a
n
)
线性表示 方式:L=(a_1,a_2,_3,......,a_n)
线性表示方式:L=(a1,a2,3,......,an)
基本区分概念:
直接前驱:某元素(前)相邻元素(除了表头元素和表尾元素)
直接后继:某元素的(后)相邻元素(除了表头元素和表尾元素)
前驱:除了直接前驱的元素
后继:除了直接后继的的元素
重点:
区分结构
逻辑结构:表示元素一对一的相邻间的关系
存储结构:指元素在计算算计存储方式,主要区分方式:元素内存地址是否连续(顺序表-链表-二叉树)是指计算机的内存映射。
问题来了一下数据什么结构:
二叉树:二叉树是一种逻辑结构,因为实际存储的过程中二叉树能体现元素和元素之间的关系(默认的指一种非线性逻辑结构)
线索二叉树:线索二叉树加入之后,能够体现元素和元素之间的关系,借助计算语言体现数据结构的特性,属于存储结构。
逻辑结构和存储结构的区别点在于
:
数据的逻辑结构是独立于在计算机中的存储结构的,数据的存储方式有多种不同的选择。例如栈是一种逻辑结构,它可以用顺序存储也可以用链式存储
数据结构是既可以描述逻辑结构又可以描述存储结构和数据运算,必须包含以上三种元素。所以像顺序表、哈希表、单链表都是数据结构。
2.ADT抽象数据结构类型
一般使用三元组表示方式:ADT={D,S,P} (D是数据对象 S是D上的关系 P是D上的操作)
定义抽象数据类型格式:
ADT 抽象数据类型名{
数据对象:<数据对象的定义>
数据关系:<数据关系的定义>
数据操作:<基本操作定义>
}
数据类型定义:
//定义顺序结构和链式结构的结构体声明
#define MAX 50 //定义顺序存储结构容量
typedef char ElementType; //元素类型
//顺序结构 动态分配方式
typedef struct list{
ElementType *ele;
int size;
int length;
}list,*listPointer;
//单向链式存储结构
typedef struct linklist{
ElementType data;
linklistPointer next;
}linklist,*linklistPointer;
//双向链式存储结构
typedef struct linklist{
ElementType data;
linklistPointer rear;
linklistPointer front;
}doubleLinklist,*doubleLinklistPointer;
//静态链表存储结构
typedef struct staticEle{
ElementType ele;
int next;
}staticEle;
typedef struct staticlist{
staticEle date[MAX];
int length;//实际存在的大小
}staticlist,*staticlistPinter;
线性表的基本操作:
InitList(* L);
Length(L);
LocateElem(L,e);
GetElem(L,i);
ListInstert(*L,i,e);
ListDelete(*L,i,&e);
PrintList(L);
Empty(L);
DestroyList(*L);
二、线性表实现
1.顺序表
核心操作:
代码如下:
//初始化表
bool init(listPointer list)
{
list.ele=(ElementType*)malloc(MAX*sizeof(ElementType));
if(!list.ele) return ; //分配失败
list.size=0;
list.length=MAX;
return true;
}
//插入数据
//C语言中应用bool类型,需要映入头文件库
//#include <stdbool.h>
bool insert(listPointer list,ElementType data,int i)
{
if(i<0||i>list.length) return false;
if(list.size==list.length)expendCapacity(list);//扩容
ElementType *pointerA=&(list.ele[i-1]);//原本的插入地址
//插入点元素后移一位
for(ElementType *pointerB=&list.ele[list.size-1];pointerB>=pointerA;--pointerB)*(pointerB+1)=*pointerB;
*pointerA=data;
list.size++;
return true;
}
//删除数据
bool delete(listPointer list,int i)
{
if(!list.ele)return false;
ElementType *pointerA=&(list.ele[i-1]);//原本的插入地址
//插入点元素后移一
for(ElementType *pointerB=&list.ele[i];!pointerB;++pointerB)*(pointerB)=*(pointerB+1);
list.size--;
return true;
}
//动态数组扩容原理
bool expendCapacity(listPointer list)
{
list.ele=(ElementType)realloc(list.ele,(list.length+20)*sizeof(ElementType));
if(!list.ele) return false;
list.length+=20;
}
1.单链表
核心操作
代码如下(示例):
//插入(头插法和尾插法)
bool insert(listPointer linst,ElementType data)
{
//头插法
listPointer find=list.next;
listPointer node=(listPointer)malloc(sizeof(linklist));
if(!node)return false;
node.data=data; //其他复杂类型的数据需要另作处理
node.next=find;
list.next=node;
//尾插法
while(find.next)find=find.next;
find.next=node;
node.next=null;
return true;
}
//删除节点
bool delete(listPointer linst,int location)
{
int count=0;
listPointer find=list.next;
listPointer prefind=list;
while(find)
{
count++;
if(count==location)break;
prefind=find;
find=find.next;
}
prefind.next=find.next;
find.next=null;
free(find);
find=null;
}
2.双链表
核心操作
代码如下(示例):双向链表的操作在循环双向链表中。
插入删除的基本模型:
实际操作模型都一样,多余的操作在实际过程中注重摧毁重建之后的指针变化情况
3.循环链表
核心操作
代码如下(示例):
//初始化操作
bool init(linklistPointer lint)
{
list=(linklistPointer)malloc(sizeof(linklist));
if(!list) return ; //分配失败
list.ele='0';//一般头节点的数据域保存该链表中元素的个数
list.next=list;//循环精髓
return true;
}
//其余操作和单单链表一样
核心操作
代码如下(示例):
//初始化
bool init(doubleLinklistPointer list)
{
list=(doubleLinklistPointer)malloc(sizeof(doublelinklist));
if(!list) return ; //分配失败
list.ele='0';//一般头节点的数据域保存该链表中元素的个数
//双向链表的精髓
list.rear=list;
list.front=list;
}
//插入数据
//插入数据可以分为头部插入和尾部插入实际是和头插法与尾插法类似
//当然也可以指定位置进行插入
bool insert(doubleLinklistPointer list,int i,ElementType data)
{
doubleLinklistPointer find=getInsertAddress(list,int i)
if(!find)return false;
doubleLinklistPointer temp=(doubleLinklistPointer)malloc(sizeof(doubleLinklist));
*(list.ele)=ele;
temp.rear=temp.front=null;
temp.front=find;
temp.rear=find.rear;
find.rear.front=temp;
find.rear=temp;
return true;
}
doubleLinklistPointer getInsertAddress(doubleLinklistPointer list,int i)
{
if(list.rear==list)return null;
int count=0;
doubleLinklistPointer find=list.next;
while(find)
{
count++;
if(count==i)break;
find=find.next;
}
return find;
}
//删除数据
bool delete(doubleLinklistPointer list,int i)
{
doubleLinklistPointer find=getInsertAddress(list,int i)
if(!find)return false;
find.rear.front=find.front;
find.front.rear=find.rear;
free(find);
return null;
}
4.静态链表(数组的方式实现)
这里的设计原则有一定的不同,这里安排是头节点利用数组的第一个存储空间作为链表的头节点,NextNodeIndex为-1时表示为尾节点
当然也可以设计其他的形式:比如利用最后一个节点的NextNodeIndex表示头节点,数组的第一个节点用于存储剩余空间的链接地址。
核心操作
代码如下(示例):
//初始化静态链表
staticlist* init()
{
staticlist* temp=(staticlist*)malloc(sizeof(staticlist));
if(!list)return ;
for(int a=0;a<MAX;a++)
{
temp->data[a].nextNodeIndex=0; //初始化O
}
temp.length=0;
return temp;
}
//插入操作(前提是合法的)
bool insert(staticlistPinter list, ElementType* data,int i)
{
int location=0;
int insertValueIndex=0;
int insert=getSpace(list);
if(-1==insert)return; //空间已满
for(;insertValueIndex<i;insertValueIndex++)
location=list->data[location].nextNodeIndex;
list.data[insert]=data;
list.data[insert].nextNodeIndex=list.data[location].nextNodeIndex;
list.data[location].nextNodeIndex=insert;
return true;
}
//申请空间
int getSpace(staticlistPinter list)
{
int index=0;
for(index;index<MAX;index++)
{
if(list->data->nextNodeIndex==0)
{
return index;
}
}
return -1;
}
//删除操作类似,需要注意其中删除后,空间会变成无数据的空间代表下次可以重新写入
//删除结点函数,num表示被删除结点中数据域存放的数据
//参考以一位博主
int deletArr(component * array, int num) {
int tempBody =maxSize-1;
int del = 0;
int newbody = 0;
//找到被删除结点的位置
while (array[tempBody].data != num) {
tempBody = array[tempBody].cur;
//当tempBody为0时,表示链表遍历结束,说明链表中没有存储该数据的结点
if (tempBody == 0) {
printf("链表中没有此数据");
return 0;
}
}
//运行到此,证明有该结点
del = tempBody;
tempBody = maxSize-1;
//删除首元结点,需要特殊考虑
if (del == array[maxSize-1].cur) {
newbody = array[del].cur;
array[maxSize-1].cur=newbody;
freeArr(array, del);
}
else
{
//找到该结点的上一个结点,做删除操作
while (array[tempBody].cur != del) {
tempBody = array[tempBody].cur;
}
//将被删除结点的游标直接给被删除结点的上一个结点
array[tempBody].cur = array[del].cur;
//回收被摘除节点的空间
freeArr(array, del);
}
}
三、线性表的应用
1、合并链表
运用如下:
//合并函数,A链表基础上进行合并
//两个链表合并到一个链表
//(区分有序或者无序)(假设有序)
linklistPointer merge(linklistPointer A,linklistPointer B)
{
linklistPointer findA=A.next;
linklistPointer findB=B.next;
A.next=null;
linklistPointer temp=null;
while(findA&&findB)
{
if(findA->data<findB->data)
{
temp=findA.next;
//头插法
findA.next=A.next;
A.next=findA;
findA=temp;
} else{
temp=findB.next;
//头插法
findB.next=B.next;
B.next=findB;
findB=temp;
}
}
if(findB)findA=findB;
if(findB)
{
temp=findB.next;
//头插法
findB.next=A.next;
A.next=findB;
findB=temp;
}
free(findB);
}
2.寻找节点共同起点地址值
代码如下:
// 获取节点
//获取两节点的长度
int getLength(linklistPointer temp)
{
int count=0;
linklistPointer find=temp;
while(temp)
{
count++;
temp=temp.next;
}
return count;
}
//用双指针法
linklistPointer getUnionParentNode(linklistPointer A,linklistPointer B)
{
if(!(A&&B))return true;
int lengthA=getLength(A);
int lengthB=getLength(B);
linklistPointer temp,PointerA=A,PointerB=B;
for(;lengthA>lengthB;lengthA--)
PointerA=PointerA.next;
for(;lengthA<lengthB;lengthB--)
PointerB=PointerB.next;
while(PoninterA.next!=NULL&&PoninterA.next!=PoninterB.next)
{
PointerA=PointerA.next;
PointerB=PointerB.next
}
return PointerB.next;
}
致谢
博文参考链接:静态链表详细参考