数据结构(1)线性表/单链表的基本实现(c)(详细的很)

------------> 切入主题
线性表是数据结构课程内最简单最基本的一种结构,其存储方式包括顺序存储链式存储,实现的基本操作包括:创建,插入,查找,输出,长度,删除数据,删除重复数据,判空,判满,合并,清空,销毁。

1.线性表的顺序存储(画个草图哈!)

在这里插入图片描述
线性表顺序存储的结构体:

typedef int ElementType;
typedef struct{
    ElementType *array; //存放数据的指针
    int length;             //已有数据个数
    int capacity;          //容量
}SeqList;

这就是一个简简单单的array数组数据,其中array是该顺序表存储数据的数组,下标是从0开始到capacity-1,其容量(最多可存储数据的个数)为capacity,
我们来观看上面的图片,不难发现,该线性表的capacity为5,length为3.
现在我们就基于此来实现一系列线性表顺序存储的基本操作。
1.创建操作函数:
该函数创建一个空的线性表,并返回其表指针!

//1.创建
SeqList *createList(int capacity)
{
	SeqList *L=(SeqList *)malloc(sizeof(SeqList));//动态申请结构体空间
	if(L==NULL) 
		return NULL    //如果动态申请失败,返回空指针
	L->length=0;      //初始线性表内无数据
	L->capacity=capacity; //给你创建的线性表赋予一定量的容量
	L->array=(ElementType *)malloc(capacity*sizeof(ElementType));	
	//给线性表数组开辟容量为capacity,ElementType类型的数组,数组存储的
	//数据类型为ElementType ,这里给int赋予了一个别名,叫ElementType,
	//所以ElementType就是int
	return L; //返回你创建的顺序表指针
}

2.判空函数:
判断该线性表是否为空

int isEmpty(SeqList *L)
{
	if(L->length==0)
		return 1;
	return 0;
}

3.输出函数:
该函数按顺序表中的顺序输出顺序表中所有的元素, 每个数后面一个空格,最后一个换行符。若无数据,则什么也不做

void printList(SeqList *L)
{
	if(L->length>0)
	{
		for(int i=0;i<L->length;i++)
			cout<<L->array[i]<<" ";
		cout<<endl;
	}	
}

4.长度函数:
其功能为返回顺序表长度(元素个数)。

int getLength(SeqList *L)
{
	return L->length;
}

5.插入函数:
在顺序表中第i个位置插入数据x,如果i不在范围内或者顺序表已满,则什么都不做。
顺序表中第一个元素的位置为1,其所在的数组下标为0.
插入数据成功,返回1;否则返回0.

int insertList(SeqList *L,int i,ElementType x)
{
	i--;
	if(i<0||i>L->length||L->length==L->capacity) return 0;
	else
	{
		for(int k=L->length-1;k>=i;k--)
			L->array[k+1]=L->array[k];
		L->length++;
		L->array[i]=x;
		return 1;
	}
}

6.查找函数:
若在线性表L中找到与x相等的元素,则返回该元素在线性表中的位置;否则,返回-1。
顺序表中第一个元素称为第1个元素,其位置为1,其所在的数组下标为0.
如果有多个相等的元素,则返回位置最靠前的那个元素的位置。

int find(SeqList *L, ElementType x)
{
	for(int i=0;i<L->length;i++)
		if(L->array[i]==x)
			return i+1;
	return -1;
}

7.获取数据函数:
获得顺序表中第i个元素的数据(保存在p指向的指针中)。
如果获取成功,返回1,否则返回0。
顺序表中第一个元素称为第1个元素,其位置为1,其所在的数组下标为0.

int getElement(SeqList *L,int i,ElementType *p)
{
	i--;
	if(i<0||i>=L->length)  return 0;
	*p=L->array[i];
	return 1;
}

8.删除(非重复)数据函数:
删除顺序表中第i个元素的数据(删除的数据保存在p指向的指针中)。
如果删除成功,返回1,否则返回0。
顺序表中第一个元素称为第1个元素,其位置为1,其所在的数组下标为0.

int delElement(SeqList *L, int i, ElementType *p)
{
	i--;
	if(i<0||i>=L->length) return 0;
	*p=L->array[i];
	for(int k=i;k<L->length-1;k++)
		L->array[k]=L->array[k+1];
	L->length--;
	return 1;
}

9.删除(重复)数据函数:
删除顺序表中的重复数据,数据表中有数据重复时, 保留最前面的数据,删除后面的重复数据。

void delRepeatElement(SeqList *L)
{
    int i=0,j=1,leng=1;
    while(j<L->length)
    {
        for(i=0;i<leng;++i)
        {
            if(L->array[i]==L->array[j])
                break;
        }
        if(i==leng)
            L->array[leng++]=L->array[j++];
        else
            j++;
    }
    L->length=leng;
}

( 这道题还有一种方法,那就是每当要删除一个数据时遍历这个数据前面所有的数,如果有重复的,就不放入数组中,如果没有,就放入,这也能起到删除重复数据的效果,大家可以试试!)

10.清除函数:
!!注意:
---->好多人有时候分不清清空操作和销毁操作的区别:清空只是将你保存的数据清空,但是你的结构并不能发生改变,比如上面那张图里面有三个数据,,清空您只需要将该数组的数据个数(长度)置于0,就好了,这样你遍历数组也不会取到数据,但有些人会问,为什么这样子就可以呢,我们知道,计算机为每个类型申请的空间地址是随机可变的,也就是说,你这个地址有数据,如果你要申请新的一块空间,一般来说是不会取你那些已经存储了数据的空间地址,所以这里不会出现所谓的段错误;而销毁,不仅仅是将数据清空,而且还要将你动态申请的一系列空间全部回收给系统(别浪费了,嘿嘿)
所以这两者有本质的区别!

清除顺序表中的所有数据(请注意数据表数据空间仍然保留)。

void clearList(SeqList *L)
{
	L->length=0;
}

11.有序合并函数:
顺序表LA和LB中的数据均有序(从小到大),将顺序表LA和LB有序(从小到大)合并到LC中,
已知初始LC为空表(空间已经分配好,并且空间足够大,即你不用担心空间不够) 。
有序!!!

void mergeList(SeqList *LA, SeqList *LB, SeqList *LC)
{
	int k=0;
	for(int i=0;i<LA->length;i++)
		LC->array[k++]=LA->array[i];
	for(int i=0;i<LB->length;i++)
		LC->array[k++]=LB->array[i];
	LC->length=k;
	sort(LC->array,LC->array+k);
}

12.销毁函数:
销毁顺序表,释放顺序表数据空间以及顺序表空间。

void destroyList(SeqList *L)
{
	//我们开辟了两次空间,一次是为线性表结构体,还有为顺序表内的数组开辟
	//了空间
	free(L->array); //销毁这个一维数组空间
	free(L);  //最后销毁这个线性表结构体
	//这两个顺序不能反,一定是先销毁线性表内部成员,然后再销毁这个线性表
}

那么现在高兴地完成了学习线性表顺序存储的操作,相信大家都有了一些对线性表的理解,现在我们来说明它的另一种存储方式——链式存储

2.顺序表的链式存储(带头结点)
在这里插入图片描述将链式存储结构体的代码放出来:

typedef int ElementType;
typedef struct Node{
	ElementType data;
	struct Node  *next;
}Node, *LinkList;

来简单介绍一下链式存储结构体内容:
1. 链式存储结构体由两部分组成,data是指该链内结点的数据域(就是放数 据的地方嘛),next是一个指针,指向下一个线性表结构体,
2.头结点: 头结点是一类特殊的结点,它没有数据域,只有一个指针域,指向下一个该数据结构类型的结构体
3. 瞄准这个结构体,Node就是这个结构体的别名,是结构体类型,而LinkList是一个指向该结构体类型的指针,下面我们来实现一些操作,
以下出现的 L 均为该链表的头结点

1.创建函数:
该函数创建一个带头节点的空的单链表。

LinkList createList()
{
	LinkList L = (LinkList)malloc(sizeof(Node));
	L->next = NULL;  //初始将链表结点的指针域置空
	return L;
}

2.输出函数:
该函数按序输出带链表L中的数据,每个数后面一个空格,最后有一个换行符,其中L是带头节点的链表。

void printList(LinkList L)
{
	Node *p=NULL;
	p=L->next;  //p是头结点相连的下一个结点
	if(p!=NULL)
	{
		while(p!=NULL)
		{
			cout<<p->data<<" "; //输出此时p结点数据
			p=p->next;  //不断遍历链表结点
		}
		cout<<endl;
	} 	
}

3.长度函数:
该函数返回链表长度,即链表L中数据结点的数目。

int getLength(LinkList L)
{
	int count = 0;
	Node *p=L->next;
	while(p!=NULL)
	{
		p=p->next;
		count++;
	}
	return count;
}

4.插入函数(头插法):
讲解一下头插法:
在这里插入图片描述
起初的原链表是这样子的,现在我们要采取头插法的方式给它再插入一个结点,但我们知道,头插法插入的新结点一定是头结点的next,同时插入新的结点后,链表的连续性一定不能发生改变!
在这里插入图片描述
在这里插入图片描述观察上面的图,在插入之前,L的next是q结点,但现在头插法插入一个结点p,此时要改变L内部next指针的指向,根据头插法的原理,新插入的结点要在头结点之后,所以L的next就是p,而p后面的结点就是原来在头结点L后面的结点q,所以p的next就是q,而此时L的next是q这条路径被销毁了,顺利成功完成了头插法操作,同时保持了链表的连续性。

该函数在链表L中用 头插法 插入数据X,其中L是带头节点的链表。

void insertHead(LinkList L, ElementType x)
{
	LinkList s; 
	s=(LinkList)malloc(sizeof(Node));//为要插入数据申请一个结点空间
	s->data=x;
	//保持链表的连接顺序
	s->next=L->next;
	L->next=s;
}

5.插入函数(尾插法):
结合上面的头插法,尾插法就是在链表的末尾插入一个新的结点
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

void insertTail(LinkList L, ElementType x)
{
	LinkList pre=L;
	Node *p=NULL;
	p=L->next;
	while(p!=NULL)  //遍历找到链表的末尾位置
	{
		pre=p;
		p=p->next;
	}
	//将新结点插入
	LinkList s;
	s=(LinkList)malloc(sizeof(Node));
	s->data=x;
	s->next=pre->next;
	pre->next=s; 
}

6.插入函数(任意位置):
经过上面两种特殊的插入方法,我就不重复啰嗦了,大致原理就是找到你要插入的位置然后开始插入,保存链表的顺序性就可以
在带头结点的单链表L中第i个位置插入值为x的结点。插入成功返回1,插入不成功返回0.

int insertList(LinkList L, int i, ElementType x)
{
	
	LinkList new_list;
	LinkList p=L;
	int k=0;
	while(p!=NULL&&k<i-1) 
	{
		k++;
		p=p->next;
	}
	if(p==NULL||i<1) return 0; //插入的位置i必须合法,而且该链表不为空。
	new_list = (LinkList)malloc(sizeof(Node));
	new_list->data = x;
	new_list->next=p->next;
	p->next=new_list;
	return 1;
}

7.查找函数(结点数据法):
该函数在带头节点的单链表L中查找是否存在结点的数据等于X,如果存在,返回该结点的指针,否则返回NULL。
如果存在多个节点数据等于x,则返回最前面一个数值等于x的节点的指针。

Node* find(LinkList L, ElementType x)
{
	Node *p=NULL;
	p=L->next;
	while(p!=NULL&&p->data!=x)
	{
		p=p->next;
	}
	return p;
}

8.查找函数(位置法):
该函数在带头节点的单链表L中查找第i个节点。如果存在,返回该结点的指针,否则返回空指针。

Node* locate(LinkList L, int i)
{
	//某结点位置是该结点的下标+1
	i--;
	LinkList p=L->next;
	int k=0;
	if(i<0||L->next==NULL) return NULL;//不合法位置 i
	while(p!=NULL && k<i)
	{
		k++;
		p=p->next;
	}
	if(k==i) return p; //找到了该结点
	else return NULL;
}

9.删除函数(结点数据法):
该函数在带头节点的单链表L中删除第一个数值为x的节点。
如果删除成功,返回1,否则返回0。

删除结点,恰恰和插入结点是相反的概念,但保存链表连接性的原理还是不能忘记的

int delNode(LinkList L, ElementType x)
{
	Node *pre=L;
	Node *p=L->next;
	while(p!=NULL&&p->data!=x)
	{
		pre=p;
		p=p->next;
	}
	if(p==NULL) return 0;
	else
	{
		pre->next=p->next;
		free(p);
		return 1;
	}
}

在这里插入图片描述在这里插入图片描述结合代码再理解一下

int delNode(LinkList L, ElementType x)
{
	Node *pre=L;
	Node *p=L->next;
	while(p!=NULL&&p->data!=x)
	{
		pre=p;
		p=p->next;
	}
	if(p==NULL) return 0;  //该链表本身就是空
	else
	{
		pre->next=p->next;//保持链表连接性
		free(p);  //p是要删除的结点,将其空间释放
		return 1;
	}
}

10.删除函数(位置索引法):
该函数在带头节点的单链表L中删除第i个节点,删除的节点的数据保持在px指向的指针中。 如果删除成功,返回1,否则返回0。
方法类似上面的删除

int delNode(LinkList L, int i, ElementType *px)
{
	LinkList p=L;
	int k=0;
	while(p!=NULL && k<i-1)
	{
		k++;
		p=p->next;
	}
	if(p==NULL || i<1 ||p->next==NULL)
		return 0;
	Node *mid=p->next;
	p->next=mid->next;
	*px=mid->data;
	free(mid);
	return 1;
}

11.清空函数:
我问了傻傻一个逗比的问题:何为清空?
傻傻答道:将所有的数据都删除?但数据结构不能发生本质改变
我又呵呵地问一句 :单链表又是怎么实现呢,它的长度好像不会关联到数据。
傻傻咽了口唾沫答道:删除除头结点后面所有的结点,因为头结点不存数据。
我板着脸,最后,哈哈大笑,给了傻傻一脑敲:你说的对,各位,傻傻都学到了,你们呢
傻傻小声bb嘟嘟一句:我又惹谁了?
我要开启清空大发了。
在这里插入图片描述
在这里插入图片描述这就是清空数据!
该函数删除单链表中的所有数据节点(保留头节点,清除后为空单链表)。

void clearList(LinkList L)
{
	Node* n;
	Node* m=L->next;
	while(m!=NULL) //从头结点开始逐个遍历
	{
		n=m->next;
		free(m);
		m=n;
	}
	L->next=NULL;
}

12.销毁函数:
销毁单链表就是在清空单链表的基础上把头结点也销毁(打掉对方的头)
该函数销毁单链表。

void destroyList(LinkList L)
{
    LinkList c1;
    while(L->next!=NULL)
    {
        c1=L->next;
        L->next=c1->next;
        free(c1);
    }
    L->next=NULL;
}

各位,经过这么一波曲折简单的学习,脑子是不是基本上掌握顺序表的数据结构实现了,下期我会放一些单链表应用的东西,有问题评论区给你们划好了。

制作不易,感谢支持,不喜勿喷!

传送门——继续学习 栈

原创文章 19 获赞 40 访问量 1万+

猜你喜欢

转载自blog.csdn.net/YSJ367635984/article/details/105268826