小黄说数据结构和算法
数据结构基础
什么是数组?
数组对应的英文是array,是有限个相同类型的变量所组成的有序集合,数组中 的每一个变量被称为元素。数组是最为简单、最为常用的数据结构。
数组的优点
什么是单项链表
链表(linked list)是一种在物理上非连续、非顺序的数据结构,由若干节点(node)所组成。
单向链表的每一个节点又包含两部分,一部分是存放数据的变量data,另一部 分是指向下一个节点的指针next。
什么是双向链表?
双向链表比单向链表稍微复杂一些,它的每一个节点除了拥有data和next指针,还拥有指向前置节点的prev指针。
数组VS链表
物理结构和逻辑结构
如果把数据结构比作活生生的人,那么物理结构就是人的血肉和骨骼,看得见,摸得着,实实在在。例如我们刚刚学过的数组和链表,都是内存中实实在在的存储结构。
而在物质的人体之上,还存在着人的思想和精神,它们看不见、摸不着。看过电影《阿凡达》 吗?男主角的思想意识从一个瘦弱残疾的人类身上被移植到一个高大威猛的蓝皮肤外星人身上,虽然承载思想意识的肉身改变了,但是人格却是唯一的。
如果把物质层面的人体比作数据存储的物理结构,那么精神层面的人格则是数据存储的逻辑结构。逻辑结构是抽象的概念,它依赖于物理结构而存在。
什么是栈
栈(stack)是一种线性数据结构,它就像一个上图所示的放入乒乓球的圆筒容器,栈中的元素只能先入后出(First In Last Out,简称FILO)。最早进入的元素存放的位置叫作栈底(bottom),最后进入的元素存放的位置叫作栈顶(top)。
什么是队列
队列(queue)是一种线性数据结构,它的特征和行驶车辆的单行隧道很相似。不同于栈的先入后出,队列中的元素只能先入先出(First In First Out,简称FIFO)。队列的出口端叫作队头(front),队列的入口端叫作队尾(rear)。
栈和队列的应用
栈的应用
栈的输出顺序和输入顺序相反,所以栈通常用于对“历史”的回溯,也就是逆流而上追溯“历史”。例如实现递归的逻辑,就可以用栈来代替,因为栈可以回溯方法的调用链
例如实现递归的逻辑,就可以用栈来代替,因为栈可以回溯方法的调用链。
2. 队列的应用
队列的输出顺序和输入顺序相同,所以队列通常用于对“历史”的回放,也就 是按照“历史”顺序,把“历史”重演一遍。
例如在多线程中,争夺公平锁的等待队列,就是按照访问顺序来决定线程在队列中的次序的。
再如网络爬虫实现网站抓取时,也是把待抓取的网站URL存入队列中,再按照存入队列的顺序来依次抓取和解析的。
散列表
散列表也叫作哈希表(hashtable),这种数据结构提供了键(Key)和值(Value)的映射关系。只要给出一个Key,就可以高效查找到它所匹配的Value,时间复杂度接近于O(1)
树
什么是树
树是n个节点的有限集,有且仅有一个特定的称为根的节点。当n>1时,其余节点可分为m个互不相交的有限集,每一个集合本身又是一个树,并称为根的子树。
在上图中,节点1是根节点(root);节点5、6、7、8是树的末端,没有“孩子”,被称为叶子节点(leaf)。
树的最大层级数,被称为树的高度或深度
什么是二叉树
二叉树(binary tree)是树的一种特殊形式。二叉,顾名思义,这种树的每个节点最多有2个孩子节点。注意,这里是最多有2个,也可能只有1个,或者没有孩子节点。
什么是满二叉树呢?
一个二叉树的所有非叶子节点都存在左右孩子,并且所有叶子节点都在同一层级上,那么这个树就是满二叉树。
什么又是完全二叉树呢?
对一个有n个节点的二叉树,按层级顺序编号,则所有节点的编号为从1到n。如果这个树所有节点和同样深度的满二叉树的编号为从1到n的节点位置相同,则这个二叉树为完全二叉树。
完全二叉树的条件没有满二叉树那么苛刻:满二叉树要求所有分支都是满的;而完全二叉树只需保证最后一个节点之前的节点都齐全即可。
那么,二叉树都有哪些遍历方式呢?
从节点之间位置关系的角度来看,二叉树的遍历分为4种。
-
前序遍历。
-
中序遍历。
-
后序遍历
-
层序遍历。
从更宏观的角度来看,二叉树的遍历归结为两大类。
-
深度优先遍历(前序遍历、中序遍历、后序遍历)。
-
广度优先遍历(层序遍历)。
深度优先遍历 深度优先和广度优先这两个概念不止局限于二叉树,它们更是一种抽象的算法思想,决定了访问某些复杂数据结构的顺序。在访问树、图,或其他一些复杂数据结构时,这两个概念常常被使用到。
所谓深度优先,顾名思义,就是偏向于纵深,“一头扎到底”的访问方式。
- 前序遍历
二叉树的前序遍历,输出顺序是根节点、左子树、右子树
- 首先输出的是根节点1。
- 由于根节点1存在左孩子,输出左孩子节点2。
- 由于节点2也存在左孩子,输出左孩子节点4
- 节点4既没有左孩子,也没有右孩子,那么回到节点2,输出节点2的右孩子节点5。
- 节点5既没有左孩子,也没有右孩子,那么回到节点1,输出节点1的右孩子节点3。
- 节点3没有左孩子,但是有右孩子,因此输出节点3的右孩子节点6。 7
- 中序遍历
二叉树的中序遍历,输出顺序是左子树、根节点、右子树。
- 后序遍历
二叉树的后序遍历,输出顺序是左子树、右子树、根节点。
广度优先遍历
如果说深度优先遍历是在一个方向上“一头扎到底”,那么广度优先遍历则恰恰相反:先在各个方向上各走出1步,再在各个方向上走出第2步、第3步……一直到各个方向全部走完。听起来有些抽象,下面让我们通过二叉树的层序遍历,来看一看广度优先是怎么回事。
层序遍历,顾名思义,就是二叉树按照从根节点到叶子节点的层次关系,一层一层横向遍历各个节点
什么是二叉堆?
二叉堆本质上是一种完全二叉树,它分为两个类型。
-
最大堆。
-
最小堆。
什么是最大堆呢?最大堆的任何一个父节点的值,都大于或等于它左、右孩子节点的值。
什么是最小堆呢?最小堆的任何一个父节点的值,都小于或等于它左、右孩子节点的值。
二叉堆的根节点叫作堆顶。最大堆和最小堆的特点决定了:最大堆的堆顶是整个堆中的最大元素;最小堆的堆顶是整个堆中的最小元素。
那么,优先队列又是什么样子呢?
优先队列不再遵循先入先出的原则,而是分为两种情况。
-
最大优先队列,无论入队顺序如何,都是当前最大的元素优先出队
-
最小优先队列,无论入队顺序如何,都是当前最小的元素优先出队
例如有一个最大优先队列,其中的最大元素是8,那么虽然8并不是队头元素,但出队时仍然让元素8首先出队。