数据结构与算法
文章平均质量分 90
数据结构面面观,经典算法透彻解析。
苏叔叔
Isolation is not good for me!
展开
-
前缀、中缀、后缀表达式
前缀、中缀、后缀表达式1.定义所谓的前、中、后,是指表达式中运算符相对于运算对象的位置。中缀运算符位于运算对象中间,即是中缀表达式。如(1+2)*3-4最普遍的、最易被人脑理解的是中缀表达式。前缀运算符位于运算对象之前,即是前缀表达式。如-*+1234后缀运算符位于运算对象之后,即是后缀表达式。如12+3*4-2.总结前缀、后缀不易被人脑理解,但易于被计算机解析。只有对中缀表达式进行合理的转换,才可得到相应的前、后缀表达式。原创 2015-06-23 16:13:09 · 3391 阅读 · 0 评论 -
二分查找的改进--差值查找
差值查找 在二分查找中,我们每次比较都可以排除一半的数据量,这个已经是很高效了。如果利用关键字本身的信息,每次排除的数据量充分依赖于关键字的大小,则查找会更高效,这就是差值查找的思想。下面通过示例代码,比较二分查找和差值查找的不同,在不同中领略差值查找的改良之处。原创 2014-09-21 00:31:09 · 4321 阅读 · 8 评论 -
线性检索:顺序检索
当我们对所检索序列中元素的分布一无所知或元素本身就是随机分布的时候,顺序检索是常用的方法。 常用的返回值策略是,若用数组array,从下标0开始存储元素,检索成功则返回相应下标,失败则返回-1。另一种返回策略是:若从下标1开始存储元素,0号位置作为sentinel(哨兵),返回0则表示检索失败。使用这种返回策略会减少循环条件的判断,提高效率。直接看代码原创 2014-08-15 20:32:31 · 3646 阅读 · 1 评论 -
集合的检索:位图法
位图法是一种逻辑上很巧妙的描述集合的方法。如集合S={2,4,1,5,12},它用位图描述就是 0110 1100 0000 1000,两个字节即可描述S,左边是低阶位。用bitset<16>存储的话就是{[15]、[14]、...[1]、[0]}={0001000000110110}。用位图对集合进行描述后,就很方便进行集合的运算,如交、并和差。原创 2014-08-15 19:53:30 · 2608 阅读 · 1 评论 -
数据结构:最小生成树--Kruskal算法
Kruskal算法 求解最小生成树的另一种常见算法是Kruskal算法,它比Prim算法更直观。从直观上看,Kruskal算法的做法是:每次都从剩余边中选取权值最小的,当然,这条边不能使已有的边产生回路。手动求解会发现Kruskal算法异常简单,下面是一个例子算法说明 为了判断环的出现,我们换个角度来理解Kruskal算法的做法:初始时,把图中的n个顶点看成是独立的n个连通分量,从树的角度看,也是n个根节点。我们选边的标准是这样的:若边上的两个顶点从属于两个不同的连通分量,则此边可取原创 2014-08-07 11:45:37 · 9981 阅读 · 1 评论 -
数据结构:最小生成树--Prim算法
最小生成树 给定一无向带权图,顶点数是n,要使图连通只需n-1条边,若这n-1条边的权值和最小,则称有这n个顶点和n-1条边构成了图的最小生成树(minimum-cost spanning tree)。Prim算法 Prim算法是解决最小生成树的常用算法。它采取贪心策略,从指定的顶点开始寻找最小权值的邻接点。图G=<V,E>,初始时S={V0},把与V0相邻接,且边的权值最小的顶点加入到S。不断地把S中的顶点与V-S中顶点的最小权值边加入,直到所有顶点都已加入到S中。原创 2014-08-05 00:30:48 · 17030 阅读 · 3 评论 -
数据结构:点对之间最短距离--Floyd算法
Floyd算法 Dijkstra算法是用于解决单源最短路径问题的,Floyd算法则是解决点对之间最短路径问题的。Floyd算法的设计策略是动态规划,而Dijkstra采取的是贪心策略。当然,贪心算法就是动态规划的特例。算法思想 点对之间的最短路径只会有两种情况:两点之间有边相连,weight(Vi,Vj)即是最小的。通过另一点:中介点,两点相连,使weight(Vi,Vv)+weight(Vv,Vj)最小。Min_Distance(Vi,Vj)=min{weight(Vi,Vj)原创 2014-08-04 13:06:24 · 4783 阅读 · 0 评论 -
数据结构:单源最短路径--Dijkstra算法
单源最短路径 给定一带权图,图中每条边的权值是非负的,代表着两顶点之间的距离。指定图中的一顶点为源点,找出源点到其它顶点的最短路径和其长度的问题,即是单源最短路径问题。Dijkstra算法 求解单源最短路径问题的常用方法是Dijkstra(迪杰斯特拉)算法。该算法使用的是贪心策略:每次都找出剩余顶点中与源点距离最近的一个顶点。算法思想 带权图G=<V,E>,令S为已确定了最短路径顶点的集合,则可用V-S表示剩余未确定最短路径顶点的集合。假设V0是源点,则初始 S={V0}。原创 2014-08-03 21:28:06 · 9991 阅读 · 0 评论 -
数据结构:图--拓扑排序
拓扑排序 在实际应用中,有向图的边可以看做是顶点之间制约关系的描述。把顶点看作是一个个任务,则对于有向边<Vi,Vj>表明任务Vj的完成需等到任务Vi完成之后,也就是说任务Vi先于任务Vj完成。对于一个有向图,找出一个顶点序列,且序列满足:若顶点Vi和Vj之间有一条边<Vi,Vj>,则在此序列中顶点Vi必在顶点Vj之前。这样的一个序列就称为有向图的拓扑序列(topological order)。步骤从有向图中选取一个没有前驱(入度为0)的顶点输出。删除图中所有以它为起点的弧。原创 2014-08-03 01:10:21 · 8152 阅读 · 0 评论 -
数据结构:图的遍历--深度优先、广度优先
图的遍历是指从图中的某一顶点出发,按照一定的策略访问图中的每一个顶点。当然,每个顶点有且只能被访问一次。在图的遍历中,深度优先和广度优先是最常使用的两种遍历方式。这两种遍历方式对无向图和有向图都是适用的,并且都是从指定的顶点开始遍历的。先看下两种遍历方式的遍历规则:深度优先深度优先遍历也叫深度优先搜索(Depth First Search)。它的遍历规则:不断地沿着顶点的深度方向遍历。顶点的深度方向是指它的邻接点方向。原创 2014-08-02 11:20:15 · 66417 阅读 · 11 评论 -
数据结构:图的实现--邻接矩阵
为了表现图中顶点之间的关联,我们可以使用邻接矩阵来实现图结构。所谓的邻接矩阵,就是一个反应边与边之间联系的二维数组。这个二维数组我们用matrix[numV][numV]表示,其中numV是顶点数。对于无权图若顶点Vi和Vj之间有边,则matrix[Vi][Vj]=1;否则matrix[Vi][Vj]=0。对于有权图若顶点Vi和Vj之间有边,且权值为weight,则matrix[Vi][Vj]=weight;否则matrix[Vi][Vj]=0或MAXWEIGHT(取最小权值或最大权值)。原创 2014-07-31 23:29:34 · 23248 阅读 · 1 评论 -
串的匹配:朴素匹配&KMP算法
引言字符串的模式匹配是一种常用的操作。模式匹配(pattern matching),简单讲就是在文本(text,或者说母串str)中寻找一给定的模式(pattern)。通常文本都很大,而模式则比较短小。典型的例子如文本编辑和DNA分析。在进行文本编辑时,文本通常是一段话或一篇文章,而模式则常常是一个单词。若是对某个指定单词进行替换操作,则要在整篇文章中进行匹配,效率要求肯定是很高的。模式匹配的朴素算法最简单也最容易想到的是朴素匹配。何为朴素匹配,简单讲就是把模式串跟母串从左向右或从右向左一点一点比较:原创 2014-07-23 22:30:13 · 4332 阅读 · 1 评论 -
图论基础
图概述图(Graph)是一种比线性结构和树形结构都要复杂的数据结构。简单讲,图是由表示数据元素的的集合V和表示数据之间关系的集合E组成。其中,数据元素常称作顶点(vertex),数据之间的关系常称作边(edge)。故图可记为G=<V,E>,其中V是顶点的有穷非空集合,E是边的集合。在图中顶点的前驱和后继是不设限制的,因此图描述的是一种网状关系。无向图若边是无序的或者说是无向的,则称此图是无向图。若无向图中有边(v1,v2)(无向图中边用圆括号表示),则显然(v2,v1)和(v1,v2)是同一条边。原创 2014-07-18 00:20:35 · 7563 阅读 · 6 评论 -
索引排序
索引排序在排序时,若是数据很复杂,对数据的移动显然是费时的。若把数据移动改为指针移动,则减少了操作复杂度。索引排序,也叫地址排序,就是这种排序思想。索引含义根据索引的含义不同,索引排序的算法上也主要分为两种。一、index[i]为array[i]最终在有序序列中的位置。二、index[i]为位置i上最终应存放元素的下标。即最终元素按array[index[0]]、array[index[1]]……有序。原创 2014-07-17 00:30:50 · 10318 阅读 · 0 评论 -
二分检索
二分检索二分检索(Binary Search)也叫二分查找,是应用于有序表上的一种检索方法。二分检索的思想是:由于序列已经有序,故不需要顺序遍历,每次只需和序列中间位置的元素进行比较即可,以此确定下次查找的位置。显然每次都可以排除一半的元素,很高效。原创 2014-07-13 15:20:32 · 3527 阅读 · 0 评论 -
哈夫曼树
哈夫曼树给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。节点的带权长度是这样定义的:节点的权值*根节点到该节点的路径长度。树的带权路径长度(Weighted Path Length of Tree,简记为WPL)则是指所有节点的带权长度和。哈夫曼树就是使WPL最小的一种树,并且哈夫曼树是满二叉树。它的构造方法是哈夫曼方法。哈夫曼树是这样构造的:原创 2014-07-12 00:03:35 · 4471 阅读 · 7 评论 -
桶排序
桶排序桶排序(Bucket Sort)是对基数排序的一个变种。在排序过程中没有用到计数数组,而是用不同的桶来暂时存储关键字。看一个示意图:整个过程就是在不断地分配、收集。在实际应用中,根据桶的创建策略,桶排序也有不同的写法。下面给出两种不同的桶创建方式。一、使用静态队列创建桶。二、使用二维数组模拟桶。原创 2014-07-11 01:07:57 · 2569 阅读 · 2 评论 -
基数排序
前言当序列中元素范围比较大时,就不适合使用计数排序。针对这种情况,就有了基数排序,这是一种按位排序。它仍然是以计数排序为基础。基数排序基数排序的基数:十进制数的基数自然是10,二进制的基数自然是2。通常有两种按位排序策略:1.高位优先法(most significant digit first,MSD):简单讲就是从高位排起。2.低位优先法(least significant digit first,LSD):它与高位优先相反,从低位排起。从排序效果上看,高位优先比较直观,但却涉及到递归的过程,故原创 2014-07-10 21:39:29 · 3371 阅读 · 0 评论 -
计数排序
前言一般的排序都是需要进行关键字的比较的。有没有不需要比较的的呢?有的,计数排序就是其中一种。计数排序假设输入序列都是0到k之间的整数,则可使用计数排序。具体操作是这样的:创建一个同类型同等大小的临时数组temp,用于备份输入序列。创建一个整型大小为k的数组count,用于统计序列中各元素出现的次数。接下来只需把备份序列从大到小放回原数组即可。一个示例图:原创 2014-07-09 22:09:15 · 2279 阅读 · 0 评论 -
堆
堆的概述堆作为一种树形结构,也是二叉树的一种应用。二叉搜索树是规定了节点之间的大小关系:左孩子<根节点<右孩子。而堆中规定的节点之间的次序是:根节点<左孩子,根节点<右孩子;根节点>左孩子,根节点>右孩子。前者我们称之为小顶堆,后者是大顶堆。不同的逻辑定义,会得到不同的性质。关于堆的一些性质描述,我们在选择排序:堆排序中已详细介绍过。堆的存储结构堆是一种特殊的完全二叉树。通常所说的堆,就是指二叉堆。特殊是指它的节点大小次序是有规定的,但其本质还是一棵完全二叉树。而完全二叉树最常用的是顺序存储结构。原创 2014-07-08 14:02:11 · 2184 阅读 · 0 评论 -
二叉树前序、中序、后序遍历非递归写法的透彻解析
前言在前两篇文章二叉树和二叉搜索树中已经涉及到了二叉树的三种遍历。递归写法,只要理解思想,几行代码。可是非递归写法却很不容易。这里特地总结下,透彻解析它们的非递归写法。其中,中序遍历的非递归写法最简单,后序遍历最难。我们的讨论基础是这样的:原创 2014-07-06 22:14:43 · 130978 阅读 · 47 评论 -
二叉搜索树
定义二叉搜索树(Binary Search Tree)或称二叉查找树,也称二叉排序树(Binary Sort Tree)。它或者是一棵空树,或者是具有下列性质的二叉树:若左子树不空,则左子树上所有节点的值均小于它的根节点的值;若右子树不空,则右子树上所有节点的值均大于它的根节点的值;左、右子树也分别为二叉搜索树;性质二叉搜索树与普通二叉树相比,有一些优秀的特征或性质:由于节点是有序排放的:左子树<根节点<右子树。故在查找一个节点的时候,只需先和根节点比较,再决定是进入左子树还是右子树查找。而原创 2014-07-05 18:14:47 · 4065 阅读 · 2 评论 -
二叉树
在计算机科学中,树是一种重要的非线性数据结构,直观地看,它是数据元素(在树中称为结点)按分支关系组织起来的结构。二叉树(Binary Tree)是每个节点最多有两个子树的有序树。通常子树被称作"左子树"(left subtree)和"右子树"(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。值得注意的是,二叉树不是树的特殊情形。在图论中,二叉树是一个连通的无环图,并且每一个顶点的度不大于3。有根二叉树还要满足根结点的度不大于2。有了根结点后,每个顶点定义了唯一的根结点,和最多2个子结点。原创 2014-07-03 19:09:49 · 11102 阅读 · 1 评论 -
树、二叉树基础
前面介绍的栈、队列都是线性结构(linear structure)。而树是非线性结构(non-linear structure)。因此,树中的元素之间一般不存在类似于线性结构的一对一的关系,更多地表现为多对多的关系。直观地看,它是数据元素(在树中称为节点)按分支关系组织起来的结构。显然,树形结构是比线性结构更复杂的一种数据结构类型。树的定义:树是含有n个节点的有穷集合,其中有一个节点比较特殊称为根节点。在图示树时,用一条边连接两个有逻辑关系的节点,这个关系被称为父子关系。二叉树(Binary Tree)由节原创 2014-07-02 12:37:01 · 4801 阅读 · 0 评论 -
归并排序:二路归并
归并排序(Merge Sort)是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个有序的子序列,再把有序的子序列合并为整体有序序列。归并排序的具体做法:把原序列不断地递归等分,直至每等份只有一个元素,此时每等份都是有序的。相邻等份合并,不断合并,直至合并完全。二路归并归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序最常用的是二路归并,即把两个小的有序的序列和并成一个大的有序序列:合二为一原创 2014-06-26 17:40:33 · 9649 阅读 · 0 评论 -
队列的应用:双端队列
双端队列(Deque:double ended queue)就是一个两端都是结尾的队列。队列的每一端都可以插入数据项和移除数据项。相对于普通队列,双端队列的入队和出队操作在两端都可进行。这里我们使用最常用的顺序结构来存储双端队列,为了节省空间,把它首尾相连,构成循环队列。并且规定left指向左端的第一个元素,right指向右端的下一个位置。那么队空的判断则是left==right,队满是(left-1+MAX)%MAX==right或者(right-left+MAX)%MAX==MAX。原创 2014-06-17 18:03:23 · 6318 阅读 · 0 评论 -
链表的应用:单元多项式的加法、减法、乘法
使用链表来实现单元多项式的加法、减法、乘法。一个单元多项式的节点结构无非是这样的:系数域、指数域、链域。我们使用链表来模拟单元多项式的常见运算。其中,加法是其它运算的基础,减法:poly1-poly2=poly1+(-poly2),乘法:poly1*poly2,可用poly1乘以poly2的每一项,相加其乘积结果。原创 2014-06-17 10:42:11 · 7402 阅读 · 7 评论 -
交换排序:快速排序
快速排序(Quick Sort)也是一种交换排序,它在排序中采取了分治策略。快速排序的主要思想是:从待排序列中选取一元素作为轴值(也叫主元)。将序列中的剩余元素以该轴值为基准,分为左右两部分。左部分元素不大于轴值,右部分元素不小于轴值。轴值最终位于两部分的分割处。对左右两部分重复进行这样的分割,直至无可分割。原创 2014-06-14 13:47:47 · 4570 阅读 · 0 评论 -
交换排序:冒泡排序
交换排序:两两比较待排序记录的关键码,若是逆置,则交换,直到无逆置。其中最简单的交换排序是:冒泡排序。冒泡排序(Bubble Sort,也叫起泡排序):不断地比较相邻的记录,若是不满足排序要求,则交换。交换时,可从前向后,也可从后向前。看一个从前向后的排序过程:原创 2014-06-12 18:24:08 · 1880 阅读 · 0 评论 -
队列的应用:优先队列
优先级队列:如果我们给每个元素都分配一个数字来标记其优先级,不妨设较小的数字具有较高的优先级,这样我们就可以在一个集合中访问优先级最高的元素并对其进行查找和删除操作了。这样,我们就引入了优先级队列 这种数据结构。 优先级队列(priority queue) 是0个或多个元素的集合,每个元素都有一个优先权,对优先级队列执行的操作有(1)查找(2)插入一个新元素 (3)删除 一般情况下,查找操原创 2014-06-12 11:26:17 · 3400 阅读 · 0 评论 -
选择排序:堆排序
堆排序(Heap Sort):使用堆这种数据结构来实现排序。先看下堆的定义:最小堆(Min-Heap)是关键码序列{k0,k1,…,kn-1},它具有如下特性:ki<=k2i+1,ki<=k2i+2(i=0,1,…)简单讲:孩子的关键码值大于双亲的。同理可得,最大堆(Max-Heap)的定义:ki>=k2i+1,ki>=k2i+2(i=0,1,…)简单讲:孩子的关键码值大于双亲的。同理可得,最大堆(Max-Heap)的定义:ki>=k2i+1,ki>=k2i+2(i=0,1,…)同样的:对于最大堆,双亲的关原创 2014-06-11 20:56:42 · 2536 阅读 · 0 评论 -
选择排序:简单选择、树形选择
选择排序(Selection Sort):经过一趟排序,可以从n-i+1(i=1,2...)个记录中选取关键字最小的记录作为有序序列中第i个记录。也就是说,每一趟排序,都会排好一个元素的最终位置。其中最简单的是简单选择排序(Simple Selection Sort)简单选择排序的思想是 在每一趟排序中,通过n-i次关键字的比较,从n-i+1个记录中选出关键字最小的记录,并和第i个记录原创 2014-06-10 17:21:33 · 7158 阅读 · 2 评论 -
队列的实现:链式队列
队列常常也使用链式存储的方式来实现。为了方便操作,同顺序存储一样,我们要维护一个头指针和一个尾指针。如下图:在链式队列中显然不会出现假溢出的情况。但在出队时,要及时释放内存。由于在队列的实现:顺序队列中,对队列的描述已经很清楚了。就闲话不多说,直接上代码:类定义和类实现#include#includeusing namespace std;typedef int ELem原创 2014-06-09 17:59:26 · 2578 阅读 · 0 评论 -
插入排序:希尔排序
我们知道当一个序列基本有序时,直接插入会变得很高效。因为此时只需少量的移动元素,操作集中在元素的比较上。基于这种想法,我们就试图把一个序列在进行直接插入前调整得尽量有序。这就是希尔排序(Shell Sort)的核心思路。(Shell只是算法发明者的名字,无特殊含义)那到底该怎么做呢? 希尔排序一反以前的做法,插入为何只在相邻的元素之间进行,不相邻的同样可以进行。于是,希尔排序也被形象原创 2014-06-07 21:15:28 · 1930 阅读 · 1 评论 -
队列的实现:顺序队列
队列(Queue)也是一种常见的线性表,它和栈相比有如下不同:队列可以在表的两端进行操作。栈只能在栈顶进行插入和删除。两端允许操作的类型不一样:可以进行删除的一端称为队头,这种操作也叫出队;可以进行插入的一端称为队尾,这种操作也叫入队。总的来说,队头只能出队,队尾只能入队。队列的示意图:模拟队列这种数据结构并不是什么难事,但会遇到一些问题,如:假溢出队原创 2014-06-07 16:26:59 · 6052 阅读 · 0 评论 -
栈的实现:链式栈
栈的链式存储,即链式栈。它相比于顺序栈,优点:插入、删除灵活 (不必移动节点,只要改变节点中的指针指向即可)。逻辑上相邻的节点物理上不必相邻。缺点:比顺序存储结构的存储密度小 (每个节点都由值域和链域组成,使用指针来表现前后节点的逻辑关系)。查找节点时链式存储要比顺序存储慢。这些优点、缺点体现了顺序存储和链式存储的相区别之处。看图就很形象了:原创 2014-06-05 21:22:15 · 4236 阅读 · 1 评论 -
插入排序:表折半插入
在前一篇插入排序:表插入中,我们用静态链表的存储方式,直接插入的策略,构建了一种新的插入排序算法:表插入。有人可能会想到:同样是静态链表的形式,为什么不使用更高效的折半插入的策略呢?这种想法真的很好,如果做到了,显然是极大的优化。 我在网上还真看到了相关的内容,大家可看下《表插入方法的改进》,里面有此想法的介绍。这篇博客就是介绍表插入的另一种实现:表折半插入。看完一定让你彻底理解它。原创 2014-06-05 17:26:57 · 2169 阅读 · 0 评论 -
插入排序:表插入
所谓插入排序之表排序,是利用静态链表的形式,分两步完成排序。一,对一个有序的循环链表,插入一新的元素,修改每个节点的后继指针的指向,使顺着这个指针的指向,元素是有序的。在这个过程中,我们不移动或交换元素,只是修改指针的指向。二,顺着指针的指向调整元素的位置,使其在链表中真正做到物理有序。思路:构建一新的结构体类型,使其封装了值域和指针域。并增加一节点,当做头节点,为循环终止原创 2014-06-02 20:44:44 · 3912 阅读 · 1 评论 -
插入排序:二路插入
在上一篇博客中:插入排序:直接插入、交换插入、折半插入。提到了三种插入排序的具体实现。不过仍有改进的地方。例如序列 2 1 3,当把1往前插入时,由于1<2,则应当把1插入到2的前面。在上述三种插入排序方法的实现中,都是把1、2位置交换。于是,我们想有没有可能不进行交换,因为交换总是相当耗时的。但是1必须要排到2的前面,可2的前面没有位置了啊?嗯,初看是这样的。试想这是一个循环的数组呢?这就是二路插入最核心的想法。原创 2014-06-01 15:47:26 · 6670 阅读 · 6 评论 -
插入排序:直接插入、交换插入、折半插入
插入排序:把一个数插入到一个有序的序列中,并要求插入后此数据序列仍然有序。这种排序思想就是插入排序。 那么对于一个原始无序的序列,哪里找有序的部分呢?这个很简单,可以把序列分为两个部分,第一个元素是第一部分,其余元素是第二部分。第一个部分只有一个元素,当然是有序的!对于如何把其余元素插入到第一部分中去,按插入策略,可分为如下几种插入方法:原创 2014-05-28 21:13:38 · 11536 阅读 · 2 评论