拓扑排序(图)、冒泡排序、插入排序

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了拓扑排序(图)、冒泡排序、插入排序相关的知识,希望对你有一定的参考价值。

参考技术A AOV网络(Activity On Vertex)
拓扑序:如果在图中从V到W有一条有向路径,则V一定排在W之前。满足此条件的顶点序列称为一个拓扑序
获得一个拓扑序的过程就是拓扑排序
AVO如果有合理的拓扑序,则必定是有向无环图(Directed Acyclic Graph,DAG)
算法:
void TopSort ( )

for ( cnt = 0 ; cnt < [ V ] ; cnt ++ )
V = 未输出的入度为0的顶点 ;
if ( 这样的V不存在 )
Error ( “图中有回路” ) ;
break ;

输出V,或者纪录V的输出序号;
for ( V 的每个邻接点 W )
Indegree [ W ]--;



改进:随时将入度变为0的顶点放到一个容器里
void TopSort ( )

for ( 图中每个顶点 V )
if ( Indegree [ W ] == 0 )
Enqueue ( V , Q ) ;
while ( ! IsEmpty ( Q ) ) ;
V = Dequeue ( Q ) ;
输出V,或者纪录V的输出序号;
cnt ++; // 计数器,纪录顶点数量
for ( V 的每个邻接点 W )
if ( —Indegree [ W ] == 0 ) //如果减完后入度为0,则将其放入容器中
Enqueue ( W , Q ) ;

if ( cnt != [ V ] )
Error ( “图中有回路” );

时间复杂度 T = O( |V| + |E| )
此算法可以用来检测有向图是否DAG

关键路径问题
AOE(Activity On Edge)网络
一般用于安排项目的工序,活动表示在边上,顶点代表活动的停止;边上写有活动持续时间
顶点包括:顶点编号、最早完成时间、最晚完成时间

两类基础算法,排序和查找
排序算法的前提前提:
void X_Sort ( ElementType A [ ], int N )
1.大多数情况下,为简单起见,讨论从小到大的整数排序
2.N是正整数,只讨论基于比较的排序(> = < 是有定义的)
3.只讨论内部排序:内存空间充分大,排序在内部空间中完成
4.稳定性:任意两个相等的数据,排序前后的相对位置不发生改变
5.没有一种排序算法是任何情况下都表现最好的

void Bubble_Sort ( ElementType A[ ] , int N )

for ( P = N - 1 ; P >= 0 ; P — )
flag = 0 ;
for ( i = 0 ; i < p ; i ++ ) // 一趟冒泡
if ( A[ i ] > A[ i + 1 ] ) // 比较相邻两个元素的大小
Swap ( A[ i ] , A[ i + 1 ] ) ;
flag = 1 ; // 标识发生了交换

// 思考:假设当程序执行到某一步时 数据已经是有序的了,但此时程序是不知道的,所以还要判断
if ( flag == 0 ) break ; // 全程无交换 数据已经有序 不需要再进从排序


最好情况:顺序 T = O ( N )
最坏情况:逆序 T = O ( N的平方 )
由于是单向执行,比较相邻的两个元素,所以适用于链表存储的数据
当两个数据相等,冒泡排序的比较过程中没有将它们交换位置,所以改算法是稳定的

void Insertion_Sort ( ElementType A[ ] , int N )

for ( P = 1 ; P < N ; P ++ ) // 假设第0个元素已经存在,从第一个元素开始插入

Tmp = A[ P ] ; // 下一个元素
for ( i = P ; i > 0 && A[ i - 1 ] > Tmp ; i -- ) // 需要插入的新元素当前从最后那个元素开始比起,比到第一个元素
A[ i ] = A[ i - 1 ] ; // 移除空位
A[ i ] = Tmp ; // 新元素落位



最好情况:顺序 T = O ( N )
最坏情况:逆序 T = O ( N的平方 )
冒泡元素是两两交换 需要涉及到三步,插入排序元素向后错,新元素一次放入

逆序对:对于下标 i < j,如果A[ i ] > A[ j ],则称 ( i , j )是一对逆序对
每一次交换元素正好消去一个逆序对,序列中有多少个逆序对,就需要交换几次元素
时间复杂度下界:
插入排序:T( N , I ) = O( N + I ) 其中 I 是逆序对个数,如果序列基本有序,则插入排序简单且高效
定理:任意N个不同元素组成的序列平均有 N(N-1)/4 个逆序对。
定理:任何仅以交换相邻元素来排序的算法,其平均时间复杂度的下界为N平方
要提高算法效率,我们必须:每次交换不止消去一个逆序对,所以我们每次交换相隔较远的两个个元素

简单排序算法(冒泡排序插入排序选择排序)JS实现

冒泡排序

冒泡排序可能是我们接触的第一个排序算法了,比较简单也实用。

思路:依次比较相邻的两个元素,值大的就换到右边,一趟循环下来最右边就是最大的元素了。然后再从头开始,找第二大的元素,这样一直走下来,整个数组就有序了。

可以参考以下gif图,理解冒泡排序的思想。
image

那么N个数字要排序完成,总共进行N-1趟排序,每i趟的排序次数为(N-i)次,所以需要比较的次数就是[n(n-1)]/2,即时间复杂度为 O(n^2)

直接上代码:

var bubbleSort = function(array){			//冒泡排序
    var length = array.length
    for (var i = 0; i < length; i++) {
        for (var j = 0; j < length-1-i; j++) {
            if (array[j] > array[j+1]) {
                swap(array, j, j+1);
            }
        }
    }
}

var swap = function(array, index1, index2){		//交换两个元素的位置
    var aux = array[index1];
    array[index1] = array[index2];
    array[index2] = aux;
}
var createNonSortedArray =function(size){		//创建一个size大小的无序列表ArrayList
    var array = new Array();
    for (var i = size; i > 0; i--) {
        array.push( parseInt(Math.random()*10000));
    }
    return array;
}

var arr = createNonSortedArray(10000);
bubbleSort(arr);

这个代码无论是最好的情况(数组有序),还是最坏情况(数组逆序),需要比较的次数都是[n(n-1)]/2,也就是时间复杂度均为 O(n^2),但如果是最好的情况,一趟比较下来发现没有元素要交换,那么时间复杂度就能降到 O(n),故再加一个标志位,就能让冒泡排序的最好情况时间复杂度降到 O(n),如下:

var bubbleSort = function(array){
    var length = array.length,
    for (var i = 0; i < length; i++) {
        var didSwap = false;			//设置标志位
        for (var j = 0; j < length-1-i; j++) {
            if (array[j] > array[j+1]) {
                swap(array, j, j+1);
                didSwap = true;
            }
        }
        if(!didSwap) break;				//没有交换了,直接跳出循环
    }
}

插入排序

思想:一个元素就是有序的,那么从第二个元素开始,与它之前的元素作比较,如果比之前的元素小,就交换位置。直到比某个元素大,就结束此次循环,也就找到了该元素应该在的位置了。把所有元素都走一遍,整个数组就有序了。

可以参考以下gif图,理解插入排序的思想。
image

在插入排序中,当待排序数组是有序时,是最优的情况,只需当前数跟前一个数比较一下就可以了,这时一共需要比较N-1次,时间复杂度为 O(n)。

最坏的情况是待排序数组是逆序的,此时需要比较次数最多,总次数记为:1+2+3+…+N-1,所以,插入排序最坏情况下的时间复杂度为 O(n^2)。

var insertionSort = function(array){
    var length = array.length,
        temp, j;
        for(var i = 1; i<length; i++){
        j = i;
        temp = array[i];
        while(j > 0 && array[j-1] > temp){
            array[j] = array[j-1];
            j--;
        }
        array[j] = temp;
    }
}

选择排序

思想:从数组的第一个元素开始,一趟循环,找出最小的,放到第一个元素的位置,然后从第二个元素开始,重复之前的步骤,直到数组整个有序。

可以参考以下gif图,理解选择排序的思想。
其中橙色表示当前排序趟的最左侧元素,绿色是这趟排序找到的最小元素的位置,灰背景是当前比较的指针。
image

时间复杂度其实与冒泡排序类似,也是一个 O(n)复杂度的算法。
代码如下:

var selectionSort = function(array){
    var length = array.length,
        indexMin;
    for (var i = 0; i < length-1; i++) {
        indexMin = i;
        for(var j = i;j<length;j++){
          if(array[indexMin] > array[j]){
            indexMin = j;
          }
        }
        if(i != indexMin){
          swap(array, i, indexMin)		//与之前冒泡排序swap方法一致,交换元素位置
        }
    }
}

以上是关于拓扑排序(图)、冒泡排序、插入排序的主要内容,如果未能解决你的问题,请参考以下文章

图 - 拓扑排序 (二)

求详解 pascal 拓扑排序

5.6 拓扑排序

图的拓扑排序是否不唯一的?

『拓扑排序』拓扑排序模板

拓扑排序