数据结构整合--线性存储结构&栈&队列&树&图

1.线性存储结构--前驱后继的唯一对应

注意的点:

①线性表在插入的时候,注意是最后一个元素开始后移,然后逐渐的前面的元素再后移

②线性表的删除--从删除元素的后一个元素开始前移,然后逐渐的后面的元素前移

①②是为了防止在移动元素的时候造成覆盖的问题,因此需要采取这样的移动的策略。

③链表在进行修改的时候--尤其是插入的时候,注意永远都是新结点先来指;

而且在不很关心次序的表中,建议玩无限头插,除非有尾指针,不然一直尾插的话会有很麻烦的遍历链表直到找到空针的问题。

因此建议头结点和尾结点均要书写!头结点&尾结点的存在意义主要在于操作的统一性,使得空表与

④链表也有一种查找的方法是使得find方法返回元素的位置是第几个,然后后续跑循环找插入位置的时候循环就改为for(int i=0;i<index;i++){p=p->next;},由此寻找插入的结点。但是不如find方法直接找到待插入的结点的前一个节点来的快--这种思想倒是可以稍作学习。不过也是有好处的,可以模拟出数组的随机存取访问的形式,比如list1[5],在重载的[]内部就可以调用find函数,来实现不同容器的访问存取的统一性

⑤双向循环链表中,头结点&尾结点可以省去一个,比如头结点可以用尾结点->next来代表,尾结点可以用头结点->front来代表

2.基于线性存储结构衍生的顺序栈&队列:

PART Ⅰ:理论向:

①栈--栈顶&栈底重合为空,注意增减元素的时候只动栈顶针。注意当只需要一个存储结构而没有约束要求(即没有先进先出这类的要求)时,也就是队列&栈都可以用的时候,用栈,因为栈的操作简单,没有队列那么多的判定公式之类的。(其实这种的时候耍list等也可以)

②队列--在表的前端进行删除,表的后端进行插入--重点在于它的应用:

一、主要是解决主机设备&外部设备的速度不匹配

二、主要来解决多用户的资源竞争,比如淘宝的秒杀

队列的一些计算方面:

(1)当队尾指针+1(插入元素)时,尾指针rear的值=(rear+1)%MaxSize,头指针同样(删除元素时才需要+1,也遵循同样的操作),实现“循环”,避免假溢出

(2)判满:(rear+1)%MaxSize=front;即少使用一个空间。这里取余主要是避免这种情况:

(3)判空:非常朴素的front=rear

(4)计算队列中的数据元素的个数:(rear-front+MaxSize)%MaxSize,这里取余主要是为了避免rear-front得到负值。所以这个式子进行计算的时候也可以改进成abs(rear-front)

③双向队列:它实际上,就是一个包含了左端右端插入&删除功能的数据结构,你想把它当成栈用,那么就遵守左端插入左端弹出或者右端插入右端弹出的原则来使用,当队列来用的话也是同样的道理。即,它并不是可以到处窜,仍然具有很强的约束性,只是它可以既被当作栈用又可以被当作队列用,依据你的规定来使用即可。

PART Ⅱ:代码的实现方面:

①栈:太√8朴实了,只是注意插入&弹出的时候判满&判空就可以了

②队列:同样注意判满判空,然后注意一下插入&删除的时候的指针的移动就可以了--遵循队列的处理原则

③双向队列:比较特殊。这里的判满就不再可以用队列的判满方法了,因为会涉及栈的操作,而栈没法实现像队列一样的循环,因此可以加一个size变量来存储当前元素个数,与总元素个数来比较判满&空即可。

注意一点,无论是左侧插入还是左侧删除,挪动的都是左指针!同样的,右侧也是。拿左侧的操作举例:左插的时候,左右指针均要挪到最右端,插入时left就--,左边出元素(拿来当栈用)就left++,右边出(拿来当队列用)就right--。

PART Ⅲ:一些补充:

①延迟队列:

只针对于JAVA?

②一些无聊的缓存淘汰机制:

(1)LRU(Least Recently Used)缓存淘汰:

1、新数据插⼊到链表头部

2、每当缓存命中(即缓存数据被访问),则将数据移到链表头部

3、当链表满的时候,将链表尾部的数据丢弃 这⾥我们是根据时间来来判断的,就是最近最久未使⽤的。如果根据使⽤次数来判断,做缓存的 命中,那就叫做LFU(Least Frequently used)。⽬前Redis应该就是⽤的LFU。

(2)LFU机制的一些简单思想:

③阻塞队列:也是针对于JAVA?

④优先级队列--二叉堆:

3.基本的树:

①三种表示法:

(1)爹地表示法:用线性表的时候很正常,但注意用链表的时候,不要忽略链表这种存储结构本身自带的next指针!既然用了链表的存储结构,自然是可以保有它原本的一些东西的!

(2)孩子表示法:类似于邻接表,一个一维数组存所有的数据,边上接出来的链表只是用来存储节点之间的关系的

(3)孩子兄弟表示法--孩子指针&右兄弟域,方便查找

注意只有(1)在使用链表的时候还需要借助链表本身的next指针来实现正常的遍历的效果,(2)(3)等其他的存储结构都是可以忽略的,因为自己的表示从属关系的指针就足够用了。

而且基于(1)(2)(3)这三种表示法,如果对遍历有别的需求--秉承着遇事不决加变量的态度,大可写出一个孩子兄弟双亲表示法,也就是一个结点既有右兄弟域又有双亲指针域又有孩子指针域。

※写二叉排序树的题写多了会有个通病--也就是插入的时候习惯了没有过多条件的插入,也就是给一个结点的值,就顺着左小右大的原则插入就可以了。但是对于一般的树,往往插入的时候需要父节点的值,使得它插入到对应的父亲下面。(所以常常会专门写一个find_parent方法)这个部分代码实现的时候要注意,不然很容易因为觉得缺条件而又习惯了不给条件发懵。

※再一个:当有结点值重复的情况时,可以在定义Node的时候在内部成员里多加一个Size,这样到时候直接把对应Node的size++就可以了,代表这个结点代表的值其实在这棵树里有两个。这种的主要是在二叉平衡树中有用,也就是跑中序遍历的时候,如果size!=1,那就在跑到这个节点的时候额外输出几份相同的数据就可以了,仍然是能够保证中序遍历的结果非降序的条件的。

※孩子兄弟表示法在具体操作的过程中,注意找完父节点之后仍然要写判断:

void insert(int key, int parent)
{
	//定位结点 递归

	Node* temp;//假如定位好了
	if (temp == NULL)
	{
		//没有这个结点
	}
	//定位好之后也只是知道应该在这个结点的周围插
	//因此仍然是需要判断一下儿子&兄弟是不是==0的
	else
	{
		//儿子空,插儿子,儿子不空,插儿子的兄弟那去
		if (temp->child == NULL)
		{
			Node* node = (Node*)malloc(sizeof(Node));
			//先操作新节点
			node->key = key;
			node->child = NULL;
			node->sibling = NULL;
			temp->child = node;
		}
		else
		{
			//儿子不空的话只能是儿子的兄弟空了
			temp = temp->child;
			Node* node = (Node*)malloc(sizeof(Node));
			node->key = key;
			node->child = NULL;
			//兄弟插--无限头插即可
			//这样依然可以满足父子的对应关系
			node->sibling = temp->sibling;
			temp->sibling = node;
		}
	}
}

注意这里耍头插,是完全可以的,因为这里并不是在搜索树等中操作,所以儿子之间是没有先后顺序的要求的,因此一直插就可以了,只要能够保证同属于一个爹。

②关于递归:

详见我的另一篇博文:基于一些题目的基础上,对于递归的初步理解(无非就是出口条件+找等效关系)

③二叉树:重点关注“层次结构”(也引出了层序遍历的操作)

(1)最基本的对于二叉树的定义,还没有涉及满二叉&完全二叉,仅仅只是“最多两个孩子”,就可以称作二叉树了。下图为一些性质,一些只有考试&做题才会需要的无意义的性质:

 注意[Logx]起到的作用实际上是使得某个范围内的整数=某一整数,这种处理值得学习

(2)满二叉树:结点度数0/2,且==0时只能是叶子

(3)完全二叉:上->下,左->右排列,leetcode里边的树的结点的给出顺序也是遵照这样一个顺序。

一些方便的否定一棵树是完全二叉的方法:

完全⼆叉树的叶结点只能存在于最下两层,其中最下层的叶结点只集中在树结构的左侧,⽽倒数第⼆层的叶结点集中于树结构的右侧。当结点的度为 1 时, 该结点只能拥有左⼦树。

例如:

 完全二叉树用顺序表存还能好点,因为用顺序表存的话无非就是从上到下从左到右的顺序一个个塞,如果要是斜树,会有很多的空间为NULL,浪费很多的空间。

(4)二叉树遍历--冲递归。注意递归的每个函数副本都是个独立的单位,它们内部的代码都是会被完整执行完的,也就是都是做的完整调用!

(5)层序遍历:一层层扩散向下遍历。(迷宫算法?)

(6)扩展二叉树:用#之类的符号来补全空了的结点。但注意用来补的这个符号一定要是树中的数据取不到的,比如一棵正数树,可以用-1来作补全。

④二叉排序树&二叉平衡树:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值