基本上耳熟能详的2个概念了,之前总是赶到招聘的时候,才会折腾翻一下,总觉得没什么用,但是随着工作以及网上的阅读,发现,其实,用处真的是太大了。
好早之前就买了《算法导论》这部巨著,然而,现在依然束之高阁,最近想着从简单点的看起,于是翻了翻《大话数据结构》《啊哈!算法》,因为也还没看完,下面就想到哪说到哪咯。
队列:先进先出的一种数据结构(FIFO),在树的层序遍历里以及广度搜索算法中,都有用到,再简单点,像数组啦、vector啦,都是类似的东东,在这里,有必要分享一个忧桑的故事:话说有一天,废柴君兴致勃勃的去参加面试,一路各种low爆,终于,面试官忍无可忍的问了废柴君一个基础问题:队列说从头进还是从尾进?那一刻,废柴君内心是崩溃的,是迷茫的,只记得先进先出啊,头进尾出或者是尾进头处,不都一样么……好吧,后来记住了,原来队列是尾进!主要参考小时候排队,都是从后面进到队伍中,从前面一个个走的。
栈:后进先出(LIFO),一般比较熟悉的应该就是压栈出栈之类的操作,在函数调用之类的地方,总会被各种压来出去的,所以这里好像有个说法,太多的递归调用,是不好的。以前对栈什么感觉,一直到前一阵做一个模仿文件夹“后退前进”操作的时候,当时是各种煎熬啊,生生了折腾了1天,各种数组、各种flag,虽然当时是弄出来了,但是回头再看的时候,连自己都蒙圈。直到前几天翻到栈这里,原来折腾半天,一个现成的数据结构摆在那里,我却没有去珍惜。回去优化了一下,很好,自己看着真心舒服很多。
数组和链表:这两种基本上就很常用了,数据呢,是一段连续的内存,随机访问速度很快,但是针对数组中间数据进行操作的话,就会十分麻烦,而链表则不然,对链表的中间数据操作起来倒是比较方便,可是说到随机访问,就不行了,必须要一个个的遍历过去。
基本上典型点的代表,就是STL中的vector以及list了,而针对前面说过的队列和栈,其实用数组或链表,都可以分别实现的,再专业点的说法叫做:顺序存储结构和链式存储结构。
树:树就是不包含回路的连通无向图,二叉树里面最全的,叫满二叉树(一个点都不少呦),可以少几个树叶(从右往左)的,叫完全二叉树。其他红黑树、B树什么的,真心没看过,下次再说。树这里,暂时能想到的,也就2个:1是遍历:前序、中序、后序,层序;2是堆:最小堆、最大堆。
这里说到的前序、中序、后序,主要是遍历的时候,读取节点的位置,读取节点,之后左子节点,之后右子节点,这就是前序,其他类似。
至于堆,主要就是用树来进行排序。最小堆就是父节点的值小于所有子节点的值,反之,就是最大堆了。
针对最小堆或者最大堆,主要有2种维护方式,分别是向下维护以及向上维护,代码像下面这样:
// 向下维护最小堆
void siftDown(int i)
{
// 假设heap已经是最小堆了
int t = 0;
while (i*2<n && !bFlag)
{
if (heap[i] < heap[i*2])
{
t = i;
}
else
{
t = i*2;
}
if (i*2+1<n)
{
if (head[t] > heap[i*2+1])
{
t = i*2+1;
}
}
if (i == t)
{
bFlag = true;
}
else
{
swap(i, t);
i = t;
}
}
}
// 向上维护最小堆
void siftUp(int i)
{
// 假设heap已经是最小堆了
if (i == 1)
{
return;
}
if (heap[i] > head[i/2])
{
return;
}
else
{
swap(i, i/2);
siftUp(i/2);
}
}
至于堆的应用,比如想从小到大排序,可以先建立最小堆,然后每次把根节点拿出来,之后将最后的叶子节点放上去,维护一下最小堆,再次重复拿出根节点,就可以啦。
堆排序还有一种更好的方法,从小到大排序的时候,不建立最小堆而建立最大堆,之后每次讲根节点,与最后的节点互换,这样还不多占用空间了呦。
堆还经常被用来求一个数列中第k大的数:只需要先将数列中前k个元素拿出来,组成一个最大堆,之后后面的元素依次比较就可以了。
最后还有图:好吧,这里除了Folyd算法,其他的真心没看懂……
以上部分内容节选自:《大话数据结构》《啊哈!算法》感谢前人栽树。