栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

线性表(List)的C实现

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

线性表(List)的C实现

写在前面的:

        数据结构的学习是从线性表开始的,由于我们对新知识的很多概念并不是很清除,因此入门的List学习起来会十分困难。

        当我们学会了List之后,就搭建好了数据结构的基本思维模式,往后学习便会比较轻松。而且栈和队列其本质都是List,只是有某些特殊性质而已。

        综上所述,List这块的内容值得你花比较多的时间来啃,性价比较高。

操作:

        初、创、销、增、删、清、查、判——初始化、创建、销毁、增加、删除、清空、查询、判断是否为空(所有的数据结构都需要这几种操作)——任何操作在执行之前都要先区分它所处的不同条件。例如:如果要删除线性表的元素,首先要保证线性表不为空,否则删除元素的操作不成立

形参:

        1、当参数需要被改动时,则使用指针(或引用)作为形参

        2、当参数不需要被改动时,则使用普通的变量作为形参

//将属于Lb但不属于La的元素插入La中
//List* La————>La是个指针,它指向一个类型为List的变量;*La表示它指向的变量,该变量类型为List
void umion(List* La,List Lb){//数组La需要被改变内容,而数组Lb不需要被改变内容
    int La_len,Lb_len,i;    //La_len表示数组La的长度,Lb_len表示数组Lb的长度
    ElemType e;//数据元素e的类型与La和Lb中存放的数据元素的类型一致
    La_len=ListLength(*La); Lb_len=ListLength(Lb);//求线性表La和Lb的长度,即给La_len和Lb_len赋值;此处La是个指针,长度为4字节,*La才是我们关注的数组
    for(i=1;i<=Lb_len;++i){//遍历Lb
        GetElem(Lb,i,&e);//将Lb第i个元素返回给e;函数声明:GetElem(L,i,*e)
        if(!LocateElem(*La,e))//判断La中是否存在于e相等的元素;函数声明:LocateElem(L,e)
            ListInsert(La,++La_len,e);//将e插入到la中,并将La的长度加一        
}
 线性表的顺序存储结构

用静态数组(固定长度的数组)来存储线性表中的数据信息。其本质就是对定长数组的一种应用,故在使用前,必须要能预估数组的大概长度;当数组的长度不确定时,不能使用循序存储结构

属性:

1、存储空间的起始位置:数组名data(data[0]的位置)

2、线性表的最大存储容量:数组长度MaxSize,是个定值

3、线性表的当前长度:length(表中数组元素的个数,随着插入和删除操作,该值会改变)

优点:

1、无需为表示元素间的逻辑关系而增加额外的存储空间,存储密度高

2、可快速读取任何位置的元素        

缺点:

1、插入和删除操作较慢

2、需要大片连续空间;当线性表长度变化较大时,难以确定其存储空间

3、造成存储空间的“碎片”

碎片的产生:

顺序表占用一整块连续的存储空间。若10个不同大小的顺序表L1~L9按从左到右的顺序排列;假设L5占10个字节,当L5被释放掉,且L4和L6始终驻留,L5中只能存放小于或等于10个字节的数据。这样必然会留下更小的缝隙,而这种更小的缝隙是可以在程序运行时逐渐积累数量的。如果一个程序长时间运行,并且以一定频率产生这种缝隙,久而久之,这类缝隙越来越多,且所有缝隙无法被使用。这就是所谓的碎片。

//线性表的顺序存储结构

#define MAXSIZE 20
typedef int ElemType;
typedef struct {    
    ElemType data[MAXSIZE];//数组中存放的元素为int型,ElemType代表该数组是个数据类型
    int length;
}SqList;//SqList是匿名结构体的别名

//返回第i个位置的元素
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
Status GetElem(SqList L, int i, ElemType* e) {//返回值为int类型,Status代表返回值表状态;不改变L的值,但改变e的值
    if (L.length == 0 || i<1 || i>L.length)
        return ERROR;//表为空、数组中没有位置i
    *e = L.data[i - 1];//用数组的下标[]来访问数组
    return OK;
}//运行结果:函数回0,代表查找错误;函数返回1,代表查找成功

//在第i个位置插入元素e
Status ListInsert(SqList* L, int i, ElemType e) {//改变L的值,但不改变e的值
    if (L->length == MAXSIZE)   return ERROR;
    if (i<1 || i>L->length + 1)   return ERROR;
    if (i <= L->length) {
        for (int k = L->length - 1; k >= i - 1; --k)
            L->data[k + 1] = L->data[k];//从后往前,将每个元素后移一位
    }
    L->data[i - 1] = e;//将e插入下标i-1处
    L->length++;//数组长度加1
    return OK;
}

//删除第i个位置的元素
Status ListDelete(SqList* L, int i, ElemType* e) {//改变L和e的值
    if (L->length == 0)   return ERROR;
    if (i<1 || i>L->length )   return ERROR;
    *e = L->data[i - 1];
    if (i <= L->length) {
        for (int k = i; k length; ++k)
            L->data[k - 1] = L->data[k];//从前往后,将每个元素前移一位
    }   
    L->length--;//数组长度减1
    return OK;
}
线性表的链式存储结构
typedef struct Node {    //定义单链表结点类型,即定义一个单链表;因为要指向结构体本身,所以结构名“Node”不能省略
	ElemType data;    //定义一个数据域
	struct Node* next;    //定义了一个指向自身结构类型的指针
}Node, *LinkList;    //LinkList=struct Node*;next域中存储的值的类型为LinkList


//初始化一个单链表(不带头结点)
bool InitList(LinkList* L){    //要传入头指针L的地址,否则函数不能对头指针L本身进行修改
    L=NULL;    //空表,暂时没有任何结点(防止有遗留的脏数据);头指针指向NULL说明即使第一个结点也不存在;链表的总长度为4字节
    return true;
}
void test(){
    LinkList L;    //定义了一个头指针L(等价于定义了一个链表L),该头指针指向一个类型为Node结点
    InitList(&L);
    cout << sizeof(L) << endl;    //输出为:4
}

链式存储结构是指针和数据结构的的一种使用场景

        此链表结点的名称为Node,第1~n个结点的名称分别为S1~Sn;头指针只占4字节,存放链表S1的地址;链表S1所占的空间为sizeof(ElemType)+4字节,sizeof(ElemType)中存放S1中的数据,余下的4字节存放结点S2的地址;……

        因此只需要知道头指针的值,就可依次找到链表的所有结点。因此定义一个头指针就等价于定了一个链表;此后当存放第一个数据时,再去定义第一个结点S1,存放第二个数据时,再去定义第二个结点S2,…….

        若链表中有n个元素,则它所占的内存空间为:4+n*(sizeof(ElemType)+4)——第一个4为头指针所占的空间,第二个4为每个结点中指针域所占的空间

要存储一个链式的线性表 ,只需要存储这个单链表的头指针;只需要知道了头指针,便能顺着头指针知道链式存储结构剩下的全部内容。

属性

结点=数据域+指针域

结点:数据元素ai的存储映像。数据域:存储数据元素信息的域。指针域:存储直接后继位置的域(其中存储的链指向数据结构本身)

单链表:此链表的每个结点中只包含一个指针域

最后一个结点的指针:为空;常用NULL或 ^ 符号来表示

头指针:1、指向第一个结点的指针;若头结点存在,则其指向头结点的指针(整个链表的存取就必须是从头指针开始进行)

               2、头指针具有标识作用,所以常用头指针冠以链表的名字

               3、无论链表是否为空,头指针均不为空

头结点:1、是为了操作统一和方便而设立的,放在第一个元素的结点之前。其数据域一般无意义,也可存放链表的长度

               2、使得在第一个元素结点前插入、删除第一个结点的操作,与其它结点的操作统一

               3、头结点可以不存在

注:不带头结点,对第一个数据结点和后续数据结点的处理需要用不同的代码逻辑,对空表和非空表的处理需要用不同的代码逻辑。带头结点,写代码更方便,用过的都说好。

线性表为空表:头结点的指针域为“空”

优点:不要求大片连续空间,改变容量方便

缺点:不可随机存取,要耗费空间存放指针

 基本概念(这几个我感觉比较绕,当时理了好长时间):

LinkList L:并没有创建一个结点,只是声明了一个指向单链表/结点的指针,即头指针。想要存储一个单链表,只需要存储它的头结点就可以了;通过头结点就可知道单链表剩下的所有内容
p:存放在结点ai-1的指针域中,类型为LinkList,代表结点ai(可视为结点ai的别名);即指向第i个结点的指针
p->data:结点ai的数据域
p->next:存放在结点ai的指针域中,类型为LinkList,代表结点ai+1(可视为结点ai+1的别名)。即指向第i+1个结点的指针
p->next->data:结点ai+1的数据域

p->next->next:结点ai+1的指针域

typedef struct Node {    //定义单链表结点类型,即定义一个单链表;因为要指向结构体本身,所以结构名“Node”不能省略
	ElemType data;    //定义一个数据域
	struct Node* next;    //定义了一个指向自身结构类型的指针
}Node, *LinkList;    //LinkList=struct Node*;next域中存储的值的类型为LinkList

Node是定义的结点类型,而LinkList的变量指向Node这种结构类型

Node*==LinkList==struct Node*:为了提高程序的可读性,二者经常在程序中混用,选择依据是所要表示的含义:
1、强调这是一个结点,使用Node*;

2、强调这是一个单链表,使用LinkList

链表一定是从头指针(LinkList L)开始的:头指针是单链表的唯一表示,只要有了头指针,线性表的链式存储结构就已经成立 

初始化一个不带头结点的单链表,并判断其是否为空 
//初始化一个单链表(不带头结点)
bool InitList(LinkList* L){    //要传入头指针L的地址,否则函数不能对头指针L本身进行修改
    L=NULL;    //空表,暂时没有任何结点(防止有遗留的脏数据)
    return true;
}
bool Empty(LinkList){//判断链表是否为空
    if(L==NULL)    //判断头指针的内容是否为NULL
        return true;
    else
        return false;
}
void test(){
    LinkList L;    //声明一个指向单链表的指针
    InitList(&L);
}
 初始化一个带头结点的单链表,并判断其是否为空 
//初始化一个单链表(带头结点)
bool InitList(LinkList* L){    //此处的L为头指针中的值,传入的是头指针的地址&L
    L=(Node*)malloc(sizeof(Node));    //分配一个名为L的头结点,其内存地址存放在头指针里;L->data是头结点L的数据域,L->next是头结点L的指针域
    if(L=NULL)    //内存不足,分配失败
        return false;
    L->next=NULL;    //头结点之后暂时还没有结点
    return true;
}
//判断单链表是否为空(带头结点)
bool Empty(LinkList L){
    if(L->next==NULL)    //判断头结点的指针域是否为NULL
        return true;
    else
        return false;
}
void test(){
    LinkList L;    //声明一个指向单链表的指针
    InitList(&L);
}

 LinkList L = (Linklist)malloc(sizeof(Node));

LinkList L一般指一个头指针

申请一块长度为sizeof(Node)的内存空间,该内存空间的数据类型为struct Node*(此处用ListLink代替,用于强调这片内存空间是一个单链表)
增加一个新的结点:在内存中申请一个结点所需空间,并用指针L指向这个结点;要表示一个单链表时,只需声明一个头指针L,指向单链表的第一个结点

//初始条件:顺序线性表L已经存在,且1≤i≤ListLength(L)
//操作结果:用e返回L中第i个数据元素的值(对链表的查找操作需要从头结点开始)
#define ERROR 0
#define OK 1
typedef int Status;
Status GetElem(LinkList L, int i; ElemType* e) {//传送一个链表的头指针==传送一个链表(知道了头指针,就等于知道了整个单链表的信息)
	int j=1;
	LinkList p = L->next;	//创建一个指向结点的指针p,让p指向L的第一个结点
	while (p && j < i) {    //寻找第i-1个结点
		p = p->next;    //指针p的后移,即让p指向下一个结点
		++j;
	}
	if (!p || j > i)    //判断第i个结点是否存在
		return ERROR;
	*e = p->data;
	return OK;
}

//操作结果:在L中第i个结点位置之前插入新的数据元素e,L的长度加1(需要分配新的结点内存)
Status ListInsert(LinkList* L, int i, ElemType e) {
	int j = 1;
	LinkList p = *L, s;	
	while (p && j < i) {    //寻找第i-1个结点
		p = p->next;    //指针p的后移,即让p指向下一个结点
		++j;
	}
	if (!p || j > i)//判断第i个结点是否存在
		return ERROR;
	s = (LinkList)malloc(sizeof(Node));    //生成新结点,s指向这个新的结点
	s->data = e;
	s->next = p->next;//将p的后继结点赋值给s的后继
	p->next = s;//将s赋值给p的后继
	return OK;
}

//操作结果:删除L的第i个结点,并用e返回其值,L的长度减1(需要回收结点内存)
Status ListDelete(LinkList* L, int i; ElemType* e) {
	int j = 1;
	LinkList p = *L, q;	
	while (p->next && j < 1) {    //遍历寻找第i-1个结点
		p = p->next;    //指针p的后移,即让p指向下一个结点
		++j;
	}
	if (!(p->next) || j > 1)//第i个结点不存在
		return ERROR;
	q = p->next;
	p->next = q->next;//将q的后继赋值给p的后继
	*e = q->data;//将q结点中的数据给e
	free(q);//让系统回收此结点,释放内存
	return OK;
}

//随机产生n个元素的值,建立带表头结点的单链线性表L(头插法)
void CreateListHead(LinkList* L, int n) {
	LinkList p;
	int i;
	srand(time(0));//初始化随机种子
	*L = (LinkList)malloc(sizeof(Node));    //*L与p是同一中结构类型
	(*L)->next = NULL;//先建立一个带头结点的单链表
	for (i = 0; i < n; ++i) {
		p = (LinkList)malloc(sizeof(Node));//生成新结点
		p->data = rand() % 100 + 1;//随机生成100以内的数字
		p->next = (*L)->next;
		(*L)->next = p;//插入到表头
	}
}

//随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法)
void CreateListTail(LinkList* L, int n) {
	LinkList p, r;
	int i;
	srand(time(0));//初始化随机种子
	*L = (LinkList)malloc(sizeof(Node));//为整个线性表
	r = *L;//r为指向尾部的结点
	for (i = 0; i < n; ++i) {
		p = (Node*)malloc(sizeof(Node));//生成新结点
		p->data = rand() % 100 + 1;//随机生成100以内的数字
		r->next = p;//将表尾终端结点的指针指向新结点
		r = p;//将当前的新结点定义为表尾终端结点
	}
	r->next = NULL;//表示当前链表结束
}

//操作结果:将L重置为空表
Status ClearList(LinkList* L) {
	LinkList p, q;
	p = (*L)->next;//p指向第一个结点
	while (p) {//没到表尾
		q = p->next;
		free(p);
		p = q;
	}
	(*L)->next = NULL;//头结点指针域为空
	return OK;
}
//静态链表
#define ERROR 1
#define OK 1
#define MAXSIZE 1000
typedef struct {
    ElemType data;
    int cur;
}Component;
StaticLinkList[MAXSIZE];

//将一维数组space中各分量链成一备用链表
Status InitList(StaticLinkList space) {
    int i;
    for (i = 0; i < MAXSIZE - 1; ++i)
        space[i].cur = i + 1;
    space[MAXSIZE - 1].cur = 0;
    return OK;
}

//若备用空间链表非空,则返回分配的节点下标,否则返回0
int Malloc_SLL(StaticLinkList space) {
    int i = space[0].cur;
    if (space[0].cur)
        space[0].cur = space[i].cur;
    return i;
}

//在L中第i个元素之前插入新的数据元素e
Status ListInsert(StaticLinkList L, int i, ElemType e) {
    int j, k, l;
    k = MAX_SIZE - 1;
    if (i<1 || i>ListLength(L) + 1)
        return ERROR;
    j = Malloc_SLL(L);
    if (j) {
        L[j].data = e;
        for (l = 1; l <= i - 1; ++l)
            k = L[k].cur;
        l[j].cur = L[k].cur;
        L[k].cur = j;
        return OK;
    }
    return ERROR;
}

//删除在L中第i个数据元素e
Status ListDelete(StaticLinkList L, int i) {
    int j, k;
    if (i<1 || i>ListLength(L))
        return ERROR;
    k = MAX_SIZE - 1;
    for (j = 1; j <= i - 1; ++j)
        k = L[k].cur;
    j = L[k].cur;
    L[k].cur = L[j].cur;
    Free_SSL(L, j);
    return OK;
}

//将下标为k的空闲结点回收到备用链表
void Free_SSL(StaticLinkList space, int k) {
    space[k].cur = space[0].cur;
    space[0].cur = k;
}

//初始条件:静态链表L已存在。操作结果:返回L中数据元素个数
int ListLength(StaticLinkList L) {
    int j = 0;
    int i = L[MAXSIZE - 1].cur;
    while (i) {
        i = L[i].cur;
        ++j;
    }
    return j;
}

//保存A表的头节点
p = rearA->next;
rearA->next = rearB->next->next;
q = rearB->next;
rearB->next = p;
free(q);

//线性表的双向链表存储结构
typedef struct DulNode {
    ElemType data;
    struct DulNode* prior;
    struct DulNode* next;
}DulNode,*DuLinkList;

//把p赋值给s的前驱
s->prior = p;
s->next = p->next;
p->next->prior = s;
p->next = s;

//把p->next赋值给p->prior的后继
p->prior->next = p->next;
p->next->prior = p->prior;
free(p);

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/975080.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号