常用算法(冒泡、插入、选择、快速)和二叉树详解
<p class="postTitle">? <div class="postBody"> <div id="cnblogs_post_body" class="blogpost-body"> 同一问题可用不同算法解决,而一个算法的质量优劣将影响到算法乃至程序的效率。算法分析的目的在于选择合适算法和改进算法。 计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。这是一个关于代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号(Order)表述,不包括这个函数的低阶项和首项系数。使用这种方式时,时间复杂度可被称为是渐近的,它考察当输入值大小趋近无穷时的情况。 定义在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。这是一个关于代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。 算法复杂度算法复杂度分为时间复杂度和空间复杂度。其作用: 时间复杂度是指执行算法所需要的计算工作量;而空间复杂度是指执行这个算法所需要的内存空间。(算法的复杂性体现在运行该算法时的计算机所需资源的多少上,计算机资源最重要的是时间和空间(即寄存器)资源,因此复杂度分为时间和空间复杂度)。 时间复杂度1. 一般情况下,算法的基本操作重复执行的次数是模块n的某一个函数f(n),因此,算法的时间复杂度记做:T(n)=O(f(n)) 分析:随着模块n的增大,算法执行的时间的增长率和 f(n) 的增长率成正比,所以 f(n) 越小,算法的时间复杂度越低,算法的效率越高。 2. 在计算时间复杂度的时候,先找出算法的基本操作,然后根据相应的各语句确定它的执行次数,再找出 T(n) 的同数量级(它的同数量级有以下:1,log(2)n,n,n log(2)n ,n的平方,n的三次方,2的n次方,n!),找出后,f(n) = 该数量级,若 T(n)/f(n) 求极限可得到一常数c,则时间复杂度T(n) = O(f(n)) 例:算法: 则有 T(n) = n 的平方+n的三次方,根据上面括号里的同数量级,我们可以确定 n的三次方 为T(n)的同数量级 则有 f(n) = n的三次方,然后根据 T(n)/f(n) 求极限可得到常数c 则该算法的时间复杂度:T(n) = O(n^3) 注:n^3即是n的3次方。 3.在pascal中比较容易理解,容易计算的方法是:看看有几重for循环,只有一重则时间复杂度为O(n),二重则为O(n^2),依此类推,如果有二分则为O(logn),二分例如、二分查找,如果一个for循环套一个二分,那么时间复杂度则为O(nlogn)。 ?常用排序<table border="1" cellspacing="0" cellpadding="0"> |
冒泡排序(BubbleSort)的基本概念是:依次比较相邻的两个数,将小数放在前面,大数放在后面。即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此继续,直至比较最后两个数,将小数放前,大数放后。
冒泡排序流程至此第一趟结束,将最大的数放到了最后。在第二趟:仍从第一对数开始比较(因为可能由于第2个数和第3个数的交换,使得第1个数不再小于第2个数),将小数放前,大数放后,一直比较到倒数第二个数(倒数第一的位置上已经是最大的),第二趟结束,在倒数第二的位置上得到一个新的最大数(其实在整个数列中是第二大的数)。如此下去,重复以上过程,直至最终完成排序。
由于在排序过程中总是小数往前放,大数往后放,相当于气泡往上升,所以称作冒泡排序。
插入排序
有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,这个时候就要用到一种新的排序方法--插入排序法,插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为O(n^2)。是稳定的排序方法。
插入算法把要排序的数组分成两部分:第一部分包含了这个数组的所有元素,但将最后一个元素除外(让数组多一个空间才有插入的位置),而第二部分就只包含这一个元素(即待插入元素)。在第一部分排序完成后,再将这个最后元素插入到已排好序的第一部分中。
1、将指针指向某个元素,假设该元素左侧的元素全部有序,将该元素抽取出来,然后按照从右往左的顺序分别与其左边的元素比较,遇到比其大的元素便将元素右移,直到找到比该元素小的元素或者找到最左面发现其左侧的元素都比它大,停止;
2、此时会出现一个空位,将该元素放入到空位中,此时该元素左侧的元素都比它小,右侧的元素都比它大;
3、指针向后移动一位,重复上述过程。每操作一轮,左侧有序元素都增加一个,右侧无序元素都减少一个。
需要两层循环,第一层循环index表示上述例子中的指针,即遍历从坐标为1开始的每一个元素;第二层循环从leftindex=index-1开始,leftindex--向左遍历,将每一个元素与i处的元素比较,直到j处的元素小于i出的元素或者leftindex<0;遍历从i到j的每一个元素使其右移,最后将index处的元素放到leftindex处的空位处。
</span><span style="color: #0000ff">public</span> InsertSort(<span style="color: #0000ff">int</span><span style="color: #000000">[] array){
</span><span style="color: #0000ff">this</span>.array =<span style="color: #000000"> array;
</span><span style="color: #0000ff">this</span>.length =<span style="color: #000000"> array.length;
}
</span><span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span><span style="color: #000000"> display(){
</span><span style="color: #0000ff">for</span>(<span style="color: #0000ff">int</span><span style="color: #000000"> a: array){
System.out.print(a</span>+" "<span style="color: #000000">);
}
System.out.println();
}
</span><span style="color: #008000">/**</span><span style="color: #008000">
* 插入排序方法
</span><span style="color: #008000">*/</span>
<span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span><span style="color: #000000"> doInsertSort(){
</span><span style="color: #0000ff">for</span>(<span style="color: #0000ff">int</span> index = 1; index<length; index++){<span style="color: #008000">//</span><span style="color: #008000">外层向右的index,即作为比较对象的数据的index</span>
<span style="color: #0000ff">int</span> temp = array[index];<span style="color: #008000">//</span><span style="color: #008000">用作比较的数据</span>
<span style="color: #0000ff">int</span> leftindex = index-1<span style="color: #000000">;
</span><span style="color: #0000ff">while</span>(leftindex>=0 && array[leftindex]>temp){<span style="color: #008000">//</span><span style="color: #008000">当比到最左边或者遇到比temp小的数据时,结束循环</span>
array[leftindex+1] =<span style="color: #000000"> array[leftindex];
leftindex</span>--<span style="color: #000000">;
}
array[leftindex</span>+1] = temp;<span style="color: #008000">//</span><span style="color: #008000">把temp放到空位上</span>
<span style="color: #000000"> }
}
</span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span><span style="color: #000000"> main(String[] args){
</span><span style="color: #0000ff">int</span>[] array = {38,65,97,76,13,27,49<span style="color: #000000">};
InsertSort is </span>= <span style="color: #0000ff">new</span><span style="color: #000000"> InsertSort(array);
System.out.println(</span>"排序前的数据为:"<span style="color: #000000">);
is.display();
is.doInsertSort();
System.out.println(</span>"排序后的数据为:"<span style="color: #000000">);
is.display();
}
}
选择排序
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。?选择排序是不稳定的排序方法。
1、从第一个元素开始,分别与后面的元素向比较,找到最小的元素与第一个元素交换位置;
2、从第二个元素开始,分别与后面的元素相比较,找到剩余元素中最小的元素,与第二个元素交换;
3、重复上述步骤,直到所有的元素都排成由小到大为止。
需要两次循环,第一层循环i表示每轮指针指向的位置,将最小值min初始化为第i个元素,第二层循环从j=i+1开始,分别与min比较,如果小于min,则更新min的值,内层循环结束后;交换min元素和第i个元素的位置。以此类推进行下一轮循环,直到i=length时停止循环。当i=length时,说明小的元素已经全部移到了左面,因此无需进行内层循环了。
</span><span style="color: #0000ff">public</span> ChooseSort(<span style="color: #0000ff">int</span><span style="color: #000000">[] array){
</span><span style="color: #0000ff">this</span>.array =<span style="color: #000000"> array;
</span><span style="color: #0000ff">this</span>.length =<span style="color: #000000"> array.length;
}
</span><span style="color: #008000">/**</span><span style="color: #008000">
* 打印数组中的所有元素
</span><span style="color: #008000">*/</span>
<span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span><span style="color: #000000"> display(){
</span><span style="color: #0000ff">for</span>(<span style="color: #0000ff">int</span><span style="color: #000000"> i: array){
System.out.print(i</span>+" "<span style="color: #000000">);
}
System.out.println();
}
</span><span style="color: #008000">/**</span><span style="color: #008000">
* 选择排序算法
</span><span style="color: #008000">*/</span>
<span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span><span style="color: #000000"> chooseSort(){
</span><span style="color: #0000ff">for</span>(<span style="color: #0000ff">int</span> i=0; i<length-1; i++<span style="color: #000000">){
</span><span style="color: #0000ff">int</span> minIndex =<span style="color: #000000"> i;
</span><span style="color: #0000ff">for</span>(<span style="color: #0000ff">int</span> j=minIndex+1;j<length;j++<span style="color: #000000">){
</span><span style="color: #0000ff">if</span>(array[j]<<span style="color: #000000">array[minIndex]){
minIndex </span>=<span style="color: #000000"> j;
}
}
</span><span style="color: #0000ff">int</span> temp =<span style="color: #000000"> array[i];
array[i] </span>=<span style="color: #000000"> array[minIndex];
array[minIndex] </span>=<span style="color: #000000"> temp;
}
}
</span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span><span style="color: #000000"> main(String[] args){
</span><span style="color: #0000ff">int</span>[] array={100,45,36,21,17,7<span style="color: #000000">};
ChooseSort cs </span>= <span style="color: #0000ff">new</span><span style="color: #000000"> ChooseSort(array);
System.out.println(</span>"排序前的数据为:"<span style="color: #000000">);
cs.display();
cs.chooseSort();
System.out.println(</span>"排序后的数据为:"<span style="color: #000000">);
cs.display();
}
}
快速排序
设要排序的是A[0]……A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。值得注意的是,快速排序不是一种稳定的,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动
注:在待排序的文件中,若存在多个关键字相同的记录,经过排序后这些具有相同关键字的记录之间的相对次序保持不变,该排序方法是稳定的;若具有相同关键字的记录之间的相对次序发生改变,则称这种排序方法是不稳定的。要注意的是,排序算法的稳定性是针对所有输入实例而言的。即在所有可能的输入实例中,只要有一个实例使得算法不满足稳定性要求,则该排序算法就是不稳定的。?
-
划分、递归、快排
-
<span style="color: #808080">@author<span style="color: #008000"> bjh
-
<span style="color: #008000">*/
<span style="color: #0000ff">public <span style="color: #0000ff">class<span style="color: #000000"> QuickSort {<span style="color: #008000">/*<span style="color: #008000">待排序、划分数组<span style="color: #008000">/
<span style="color: #0000ff">private <span style="color: #0000ff">int<span style="color: #000000">[] array;
<span style="color: #008000">/*<span style="color: #008000">数组长度<span style="color: #008000">/
<span style="color: #0000ff">private <span style="color: #0000ff">int<span style="color: #000000"> length;<span style="color: #0000ff">public QuickSort(<span style="color: #0000ff">int<span style="color: #000000">[] array){
<span style="color: #0000ff">this.array =<span style="color: #000000"> array;
<span style="color: #0000ff">this.length =<span style="color: #000000"> array.length;
}<span style="color: #008000">/**<span style="color: #008000">
- 打印元素
<span style="color: #008000">*/
<span style="color: #0000ff">public <span style="color: #0000ff">void<span style="color: #000000"> printArray(){
<span style="color: #0000ff">for(<span style="color: #0000ff">int i=0; i<length; i++<span style="color: #000000">){
System.out.print(array[i]+" "<span style="color: #000000">);
}
System.out.println();
}
<span style="color: #008000">/**<span style="color: #008000">
- 划分
- <span style="color: #808080">@return<span style="color: #008000"> 划分的分界点
<span style="color: #008000">*/
<span style="color: #0000ff">public <span style="color: #0000ff">int partition(<span style="color: #0000ff">int left,<span style="color: #0000ff">int right,<span style="color: #0000ff">int<span style="color: #000000"> pivot){
<span style="color: #008000">//<span style="color: #008000">左指针的起点,left-1是由于在后面的循环中,每循环一次左指针都要右移,
<span style="color: #008000">//<span style="color: #008000">这样可以确保左指针从左边第一个元素开始,不然是从第二个开始
<span style="color: #0000ff">int leftpoint = left-1<span style="color: #000000">;
<span style="color: #008000">//<span style="color: #008000">右指针的起点,right+1是由于后面的循环中,每循环一次右指针都要左移,
<span style="color: #008000">//<span style="color: #008000">这样可以确保右指针从最右边开始,不然是从倒数第二个开始
<span style="color: #0000ff">int rightpoint = right+1<span style="color: #000000">;
<span style="color: #0000ff">while(<span style="color: #0000ff">true<span style="color: #000000">){
<span style="color: #008000">//<span style="color: #008000">找到左边大于pivot的数据,或者走到了最右边仍然没有找到比pivot大的数据
<span style="color: #0000ff">while(leftpoint<right && array[++leftpoint]<<span style="color: #000000">pivot);
<span style="color: #008000">//<span style="color: #008000">找到右边小于pivot的数据,或者走到了最左边仍然没有找到比pivot小的数据
<span style="color: #0000ff">while(rightpoint>left && array[--rightpoint]><span style="color: #000000">pivot);
<span style="color: #008000">//<span style="color: #008000">左指针和右指针重叠或相交
<span style="color: #0000ff">if(leftpoint >=<span style="color: #000000"> rightpoint){
<span style="color: #0000ff">break<span style="color: #000000">;
}<span style="color: #0000ff">else<span style="color: #000000">{
<span style="color: #008000">//<span style="color: #008000">交换左边大的和右边小的数据
<span style="color: #000000"> swap(leftpoint,rightpoint);
}
}
<span style="color: #008000">//<span style="color: #008000">返回分界点,即右边子数组中最左边的点
<span style="color: #0000ff">return<span style="color: #000000"> leftpoint;
}
<span style="color: #008000">/**<span style="color: #008000">
- 交换数据
<span style="color: #008000">*/
<span style="color: #0000ff">public <span style="color: #0000ff">void swap(<span style="color: #0000ff">int leftpoint,<span style="color: #0000ff">int<span style="color: #000000"> rightpoint){
<span style="color: #0000ff">int temp =<span style="color: #000000"> array[leftpoint];
array[leftpoint] =<span style="color: #000000"> array[rightpoint];
array[rightpoint] =<span style="color: #000000"> temp;
}
<span style="color: #0000ff">public <span style="color: #0000ff">static <span style="color: #0000ff">void<span style="color: #000000"> main(String args[]){
<span style="color: #0000ff">int[] array = {99,78,26,82,9,81,22,100,30,20,85<span style="color: #000000">};
QuickSort qs = <span style="color: #0000ff">new<span style="color: #000000"> QuickSort(array);
System.out.println("划分前的数据为:"<span style="color: #000000">);
qs.printArray();
<span style="color: #0000ff">int bound = qs.partition(0,array.length-1,50<span style="color: #000000">);
System.out.println("划分后的数据为:"<span style="color: #000000">);
qs.printArray();
System.out.println("划分的分界点为:" + array[bound] + ",分界点的坐标为:" +<span style="color: #000000"> bound);
} - 打印元素
}
二叉树遍历
树(Tree)是元素的集合。我们先以比较直观的方式介绍树。下面的数据结构是一个树:
树有多个节点(node),用以储存元素。某些节点之间存在一定的关系,用连线表示,连线称为边(edge)。边的上端节点称为父节点,下端称为子节点。树像是一个不断分叉的树根。
每个节点可以有多个子节点(children),而该节点是相应子节点的父节点(parent)。比如说,3,5是6的子节点,6是3,5的父节点;1,7是3的子节点,3是1,7的父节点。树有一个没有父节点的节点,称为根节点(root),如图中的6。没有子节点的节点称为叶节点(leaf),比如图中的1,5节点。从图中还可以看到,上面的树总共有4个层次,6位于第一层,9位于第四层。树中节点的最大层次被称为深度。也就是说,该树的深度(depth)为4。
如果我们从节点3开始向下看,而忽略其它部分。那么我们看到的是一个以节点3为根节点的树:
三角形代表一棵树
再进一步,如果我们定义孤立的一个节点也是一棵树的话,原来的树就可以表示为根节点和子树(subtree)的关系:
上述观察实际上给了我们一种严格的定义树的方法:
1. 树是元素的集合。
2. 该集合可以为空。这时树中没有元素,我们称树为空树 (empty tree)。
3. 如果该集合不为空,那么该集合有一个根节点,以及0个或者多个子树。根节点与它的子树的根节点用一个边(edge)相连。
上面的第三点是以递归的方式来定义树,也就是在定义树的过程中使用了树自身(子树)。由于树的递归特征,许多树相关的操作也可以方便的使用递归实现。我们将在后面看到。
树的实现
树的示意图已经给出了树的一种内存实现方式: 每个节点储存元素和多个指向子节点的指针。然而,子节点数目是不确定的。一个父节点可能有大量的子节点,而另一个父节点可能只有一个子节点,而树的增删节点操作会让子节点的数目发生进一步的变化。这种不确定性就可能带来大量的内存相关操作,并且容易造成内存的浪费。
一种经典的实现方式如下:
树的内存实现
拥有同一父节点的两个节点互为兄弟节点(sibling)。上图的实现方式中,每个节点包含有一个指针指向第一个子节点,并有另一个指针指向它的下一个兄弟节点。这样,我们就可以用统一的、确定的结构来表示每个节点。
计算机的文件系统是树的结构,比如中所介绍的。在UNIX的文件系统中,每个文件(文件夹同样是一种文件),都可以看做是一个节点。非文件夹的文件被储存在叶节点。文件夹中有指向父节点和子节点的指针(在UNIX中,文件夹还包含一个指向自身的指针,这与我们上面见到的树有所区别)。在git中,也有类似的树状结构,用以表达整个文件系统的版本变化 (参考)。
?二叉树:
特点:
(1)二叉树是有序树,即使只有一个子树,也必须区分左、右子树;
(2)二叉树的每个结点的度不能大于2,只能取0、1、2三者之一;
(3)二叉树中所有结点的形态有5种:空结点、无左右子树的结点、只有左子树的结点、只有右子树的结点和具有左右子树的结点。
二叉树(binary)是一种特殊的树。二叉树的每个节点最多只能有2个子节点:
二叉树
由于二叉树的子节点数目确定,所以可以直接采用上图方式在内存中实现。每个节点有一个左子节点(left children)和右子节点(right children)。左子节点是左子树的根节点,右子节点是右子树的根节点。
如果我们给二叉树加一个额外的条件,就可以得到一种被称作二叉搜索树(binary search tree)的特殊二叉树。二叉搜索树要求:每个节点都不比它左子树的任意元素小,而且不比它的右子树的任意元素大。
(如果我们假设树中没有重复的元素,那么上述要求可以写成:每个节点比它左子树的任意节点大,而且比它右子树的任意节点小)
二叉搜索树,注意树中元素的大小
二叉搜索树可以方便的实现搜索算法。在搜索元素x的时候,我们可以将x和根节点比较:
1. 如果x等于根节点,那么找到x,停止搜索 (终止条件)
2. 如果x小于根节点,那么搜索左子树
3. 如果x大于根节点,那么搜索右子树
二叉搜索树所需要进行的操作次数最多与树的深度相等。n个节点的二叉搜索树的深度最多为n,最少为log(n)。
<h3 dir="ltr">二叉树的遍历
<blockquote dir="ltr">
<p dir="ltr">遍历即将树的所有结点访问且仅访问一次。按照根节点位置的不同分为前序遍历,中序遍历,后序遍历。
<blockquote dir="ltr">
<p dir="ltr">前序遍历:根节点->左子树->右子树
<p dir="ltr">中序遍历:左子树->根节点->右子树
<p dir="ltr">后序遍历:左子树->右子树->根节点
?二叉树的类型
教材上的说法:一个深度为k,节点个数为 2^k - 1 的二叉树为满二叉树。这个概念很好理解,
就是一棵树,深度为k,并且没有空位。
首先对满二叉树按照广度优先遍历(从左到右)的顺序进行编号。
一颗深度为k二叉树,有n个节点,然后,也对这棵树进行编号,如果所有的编号都和满二叉树对应,那么这棵树是完全二叉树。
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!