数据结构基础学习笔记(一)线性表

线性表书上一般的定义是这样的:具有相同数据类型的n个数据元素的有限序列。
如果用L命名,L=(a1,a2,…,ai,ai+1,…,an)
如果直观一点理解的话就是数据元素之间的关系是线性的,有这样的前后关系,就像糖葫芦一样。
在这里插入图片描述

线性表的每一个糖葫芦的数据类型都要是一样的,比如一个是整数,都要是整数,或者是自己定义的结构类型,比如学生。但是数据类型都得是一样的,相应的,每一项占的空间也都是一样大的。而且线性表是有顺序的,从定义就可以看出来,有限序列,首先必须是有限个数的,而且是序列,大家都是按一定顺序排队。
除了第一个元素外,每个元素有且仅有一个直接前驱,除最后一个元素外,每个元素都有一个直接后继。
有两种物理结构可以实现线性表,一种是顺序存储方式,一种是链式存储方式。
首先说一下顺序表,就是以顺序存储的方式实现线性表。
就是把逻辑上相邻的数据元素物理位置上也相邻,而且因为每个数据元素的大小都是一样的,所以我们只要知道第一个的地址,第二个的位置就是加第一个的地址加一个数据元素的大小,第二个就是加两个,剩下的我们都可以很容易的计算出来。
c语言中使用sizeof(Elemtype)可以得到每个数据元素所占的存储空间的大小。
这是顺序存储的代码。

#define MaxSize 50			
typedef struct{				
	Elemtype data[MaxSize];	
	int length;			
}SqList;

首先定义一个常数,这个常数是线性表的最大长度,这里是50.
就相当于去图书馆占座,把这50个桌子上放上书了,但是还没有来人。然后这个顺序表的当前长度就是现在来了多少个人。
顺序表的插入和删除操作都是很麻烦的,就像排队的时候有人插队,插队的那个位置以后的人都要向后移一位。

bool ListInsert(SqList &L,int i,ElemType e){
	if(i < 1 || i > L.length+1)
		return false;
	if(L.length>=MaxSize)
		return false;
	for(int j=L.length;j>=i;j--)
		L.data[j]=L.data[j-1];
	L.data[i-1]=e;
	L.length++;
	return true;
}

在第i个位置插入新元素e,首先要判断一下这个位置i的范围是不是有效,比如说这个顺序表一共长度是10,要在20的位置插一个数,就没有什么意义对吧,或者说当前的存储空间已经满了,也不能插入,如果i合法的话,就把i以后的元素都向后移动一位,注意是先从最后边移动,如果从前变开始移动的话就会把后边的数据覆盖掉。
删除操作的流程也很相似,首先判断i的范围是不是有效,然后将被删除的元素赋值给e,然后把i以后的位置一个一个向前移动,不过这次是从前边开始移动。
还有一个常见的操作是查找顺序表中第一个值等于e的元素,这个只要按顺序一个值一个值进行比较就可以了。
顺序表的插入、删除、按值查找这些操作的时间复杂度都是O(n)。
用顺序存储结构表示线性表有很多好处,但同样有很多缺点,前边已经说过一个优点,就是它能快速读取数据,知道了首地址以后,想要查看第几个数据元素的值,直接加上几个数据元素的大小就行了,这个是顺序表最大的一个优点,这样表示也有一些问题,首先是插入删除非常复杂,如果是只有五六个数据元素的可能体会不出来,但是如果说这个线性表有几千几万个元素,插个队几万个元素都要收到牵连,那就太麻烦了。而且这个必须要占据一整块的空间,如果占了之后用不了这么多,就会造成空间上的浪费,或者后来想再插入一个新元素发现空间不够了,这都是顺序存储的缺点。
为了解决这些缺点,人们提出了链式存储结构。
链式存储:不要求逻辑上相邻的元素物理上也相邻,借助元素存储地址的指针表示元素间的逻辑关系。
链式存储的最大优点是什么呢?就是它不像顺序存储一样,必须要一大块存储空间,链式存储单元可以是连续的一大块,也可以是不连续的。只要是能放的开一个数据元素的大小就可以。
但是链式存储的数据元素不能像顺序存储一样,只存储本身的信息了,还需要存放一个指向其后续结点的指针,因为这些数据
在存储空间中不连续,如果没有指针,就不知道下一个数据元素去哪里了。
链式存储的结构是这样的:
在这里插入图片描述

除了需要一定的空间来存储指针以外,还有一个不好的地方,就是它没办法做到像顺序表一样随机存取,查找一个结点的时候,只能一个一个的遍历,因为每个结点只知道自己的下一个结点是什么,而下下个结点的位置需要到下一个结点去找。
有的链表中会设置一个头结点,这个结点不存储数据,有一个指向第一个结点的头指针。
那么为什么要设置这样一个头结点呢。如果没有头结点的话,就会有一个问题,就是对第一个结点的操作和其他结点会不一样,如果要删除第一个结点,或者要在第一个结点前插入新结点都需要重新指定哪个是新的头指针。头结点就像一群人排队有个举着旗的旗手,它永远是站在第一个,不可能有人插队到它前面,这样就统一了链表的第一个位置和其他位置的操作。
链式存储的插入和删除操作要容易的多,不会像顺序表一样,波及到一片结点。
如果我们要将一个新结点s插入到p结点后边的流程是这样的,首先令s结点的指针指向p的后继结点,然后令p结点的指针指向s结点。注意这两个步骤不能反。如果p结点的指针先指向s结点,那p指针以后的结点就找不到了。
在这里插入图片描述
在这里插入图片描述

链表删除结点的操作是这样的,比如要删除p的下一个结点,令q指向被删除的结点,然后令p的指针指向q的指针指向的结点。然后释放用free(q)释放结点的存储空间。
链表还有一个问题,比如说这个链表,我们遍历到了c这个结点, 但是突然想访问它之前这个结点之前这个结点,就是b这个结点,是做不到的,因为它没有向前的指针,只能从头再重新遍历到b。
在这里插入图片描述

怎么解决这个问题呢?再在前面加一个指针就行了。
于是就有了双链表。
双链表的结点是这样的,除了结点的数据,还有一个指向前驱结点的指针,有一个指向后继结点的指针。
在这里插入图片描述
将结点s插入到结点p之后的操作是这样的:

s->next=p->next;
p->next->prior=s;
s->prior=p;
p->next=s;

删除p的后继结点的操作是这样的:
p->next=q->next;
q->next->prior=p;
free(q);

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值