排序问题1

Posted 浪漫逆风

tags:

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

本博客的代码的思想和图片参考:好大学慕课浙江大学陈越老师、何钦铭老师的《数据结构》

 

排序

1 排序前提

1.函数的名称规范

void X_Sort ( ElementType A[], int N )



2.大多数情况下,为简单起见,讨论从小大的整数排序



3.N是正整数



4只讨论基于比较的排序( >= < 有定义 )



5.只讨论内部排序



6稳定性:任意两个相等的数据,排序前后的相对位置不发生改变



7.没有一种排序是任何情况下都表现最好的

1 冒泡排序

1.1算法思想

第一趟:比较第一个元素和第二元素,如果第一个元素比第二个元素大,就交换位置,否则就不交换位置;在比较第二元素和第三个元素的大小,如果第二个元素比第三个元素大,交换位置,否则不交换位置;然后接着比较第三个元素和第四个元素…… 直到比较到第N-1个元素和第N个元素。这样一趟下来,可以确定最大的元素在N位置

第二趟:比较第一个元素和第二元素,如果第一个元素比第二个元素大,就交换位置,否则就不交换位置;在比较第二元素和第三个元素的大小,如果第二个元素比第三个元素大,交换位置,否则不交换位置;然后接着比较第三个元素和第四个元素……,直到比较到N-2 N-1个元素。这样,第二趟下面,第二大的元素可以确定在N-1位置。

第三趟……

这样到第N趟,就可以吧数组排序完成。

1.2 伪代码描述

/*

* A method to implement bubble sort.If the bubble sort has no any swap

* operation at one time,indicating the array has been ordered.So we use a

* tag to make it effective

*@param a A integer array need to sort

*@param n The length of the array

*/

voidbubble_sort(elementType a[], int n) {

int i, j, flag;

for (i = n - 1; i >= 0; i--) {

flag = 0;

for (j = 0; j < i; j++) {

if (a[j] > a[j + 1]) {

swap(&a[j], &a[j + 1]);

flag = 1;

}

}

if (flag == 0) {

break;

}

}

}



1.3 算法分析

最好的情况:如果这个数组本身就是有序的,那么时间复杂:T=O(N)

最坏的情况:如果这个数组本身就是逆序的,那么时间复杂度为T=O(N^2)

因为只有当前面的元素严格的大于后面元素时,才会交换位置,相对位置不会发生改变,那么该算法是稳定的。



2 选择排序

2.1算法思想

选择排序的前提是保证数组中已有元素是有序的。例如在给数组的第i个元素排序(找合适位置)时,已经保证前面i-1个元素是有序的。

第一趟:当给数组中第一个元素排序时,因为就一个元素,默认就是有序的。

第二趟:当给数组中第二元素排序时,比较(前一个元素)第一个元素是否比他大,如果大,把前一个元素往后面摞一位,然后。

第三趟:给数组中第三个元素排序时,比较前一个元素是不是比他大,比他大,前一个元素向后摞一位。在比较在前面一个元素是否比他大,如果比他大,在吧在前一个元素往后摞一位;如果不比他大,那么刚才那个摞的那个空位就就放入该元素。

第四趟……

N



2.2 伪代码描述

/*

* Implemet insertion sort.

*@param a A integer array need to sort

*@param n The length of the array

*/

voidinsertion_sort(elementType a[], int n) {

int i, j;

elementType temp;

for (i = 1; i < n; i++) {

temp = a[i];

for (j = i; j > 0 && a[j - 1] > temp; j--) {

a[j] = a[j - 1];

}

a[j] = temp;

}


}





2.3 算法分析

最好的情况:如果这个数组本身就是有序的,那么时间复杂:T=O(N)

最坏的情况:如果这个数组本身就是逆序的,那么时间复杂度为T=O(N^2)

因为只有当前面的元素严格的大于后面元素时,才会交换位置,相对位置不会发生改变,那么该算法是稳定的。





3 时间复杂的下界

为了说明这个问题,我先给出一个例题:

给定初始序列{34, 8, 64, 51,32, 21},冒泡排序和插入排序分别需要多少次元素交换才能完成?

答:冒泡需要9次,选择排序也需要9次。这是一个巧合吗?

3.1 引入概念

对于下标i<j,如果A[i]>A[j],则称(i,j)是一对逆序对(inversion)

问题:序列{34, 8, 64, 51, 32, 21}中有多少逆序对?

(34, 8) (34, 32) (34, 21) (64, 51) (64, 32) (64, 21) (51, 32) (51, 21) (32, 21)

有九个逆序对。

交换两个相邻的逆序对正好消去一个逆序对!

插入排序(冒泡排序)的时间复杂度可以重新定义为:

T(N,I)=O(N,I).IN个元素中逆序对的个数

——如果需要排序的序列基本有序,则插入排序简单且高效



3.2 时间复杂度下界

定理:任意N个不同元素组成的序列平均具有N ( N - 1 ) / 4 个逆序对。

定理:任何仅以交换相邻两元素来排序的算

,其平均时间复杂度为 ( N^2 )

这意味着:要提高算法效率,我们必须

1.每次消去不止1个逆序对!

2. 每次交换相隔较远的2个元素!



4 希尔排序

4.1算法思想

定义增量序列 D M > D M-1 > ... > D 1 = 1

对每个 D k 进行“D k -间隔”排序 ( k = M, M-1, ... 1 )

注意:“D k -间隔”有序的序列,在执行“D k-1 -间隔”排序后,仍然是“D k -

间隔”有序的。下面用一幅图片来说明希尔排序的过程

 

 

4.2希尔排序的伪代码描述

4.2.1基本的希尔排序的伪代码

/*

* Implement base shell sort

*@param a A integer array need to sort

*@param n The length of the array

*/

voidshell_sort_base(elementType a[], int n) {

int i, j, d;

elementType temp;

for (d = n / 2; d > 0; d = d / 2) {

for (i = d; i < n; i++) {

temp = a[i];

for (j = i; j >= d && a[j - d] > temp; j -= d) {

a[j] = a[j - d];

}

a[j] = temp;

}

}


}



在基本的希尔排序中,我们的间隔取 n/2 n/4 n/8 … n/2^i … 1

在上面基本的希尔排序中,最坏的时间复杂度T(n)=O(n^2).很糟糕

下面举一个最坏情况的例子

4.3 希尔排序的增量序列

 

5 堆排序

5.1 引子

在介绍堆排序之前,我们先介绍一下选择排序。

选择排序的算法思想:第一次从N个元素中选择一个最小的。放入0位置,第二次从N个元素中选择第二小的,放入1位置。… 第i次从N个元素选择第i小的元素放入第i-1位置

下面是选择排序的伪代码描述

void Selection_Sort ( ElementType A[], int N )

{

for ( i = 0; i < N; i ++ ) {

MinPosition = ScanForMin( A, i, N–1 );

/* A[i]A[N–1]中找最小元,并将其位置赋给MinPosition */

Swap( A[i], A[MinPosition] );

/* 将未排序部分的最小元换到有序部分的最后位置 */

}

}

对于交换元素的操作,是线性。问题在于从N个元素每次找到最i小的元素。选择排序比较暴力,他每次都是从N个元素中选择最小的元素,然后进行交换位置。这样时间时间复杂度T(N)=O(N^2).我们如果改进找到最小元的操作?

答案是使用我们以前学过的工具,最小堆。

5.2 堆排序算法1---利用最小堆

算法思想:把用户给定的数组调整成一个最小堆,每次从堆中弹出一个最小的元素,使用临时数组保存,然后在吧临时数组的元素复制回原数组。

伪代码描述:

void Heap_Sort ( ElementType A[], int N )

{

BuildHeap(A); /* O(N) */

for ( i=0; i<N; i++ )

TmpA[i] = DeleteMin(A); /* O(logN) */

for ( i=0; i<N; i++ ) /* O(N) */

A[i] = TmpA[i];

}

通过伪代码我们可以看到,此算法的时间复杂的度为T(N)=O(NlogN),但是他需要开辟一个临时的数组O(N)来存放已经排序好的元素。而且还修养话费元素复制的时间。

5.3 堆排序算法2---利用最大堆

算法思想:首先吧数组调整成最大堆。把最大堆的第一个元素和最后一个元素调换位置。这样最大的元素就在原数组的最后。然后吧剩下n-1个元素调整成最大堆。重复上面的操作。这样每次选出最大的元素。

伪代码描述:

void Heap_Sort ( ElementType A[], int N )

{

for ( i=N/2-1; i>=0; i-- )/* BuildHeap */

PercDown( A, i, N );

for ( i=N-1; i>0; i-- ) {

Swap( &A[0], &A[i] ); /* DeleteMax */

PercDown( A, 0, i );

}

}



5.3 算法分析

1.定理:堆排序处理N个不同元素的随机排列的平均比较次数是2N logN - O(Nlog logN)

2.虽然堆排序给出最佳平均时间复杂度,但实际效果不如用Sedgewick增量序列的希尔排序。

3.堆排序好处是取出前i个最小的数。



6 归并排序

6.1有序子列归并

在介绍归并排序的算法之前,我们先来介绍一下“有序子列的归并”下面通过一张图片来介绍

 

 

下面是给出有序子列归并的源代码

/*

* Merge sub-sequence to original array.

* @param a original <code>elementType</code> array to store the elements

* @param tmpA temporary <code>elementType</code> array to store the temporary elements

* @param l The start index of left sub-sequence

* @param r The start index of left sub-sequence

* @param rightEnd The end index of left sub-sequence

*/

voidmerge(elementType a[], elementType tmpA[], int l, int r, int rightEnd) {

/*

* lefeEnd is the r-1,the sub-sequence is adjacent

*/

int leftEnd = r - 1;

/*

* tmp is the counter of the <code>tmpA</code>

* we should let <code>tmpA</code> index corresponding original array

*/

int tmp = l;

/*

* Record the quantity of the all elements

*/

int numElements = rightEnd - l + 1;

int i;

while (l <= leftEnd && r <= rightEnd) {

if (a[l] <= a[r]) {

tmpA[tmp++] = a[l++];

} else {

tmpA[tmp++] = a[r++];

}

}

while (l <= leftEnd) {

tmpA[tmp++] = a[l++];

}

while (r < rightEnd) {

tmpA[tmp++] = a[r++];

}


/*

* Put <code>tmpA</code> elements into the original array

*/

以上是关于排序问题1的主要内容,如果未能解决你的问题,请参考以下文章

算法归并排序 小和 问题

SQL 字符串排序问题

排序算法1——冒泡排序

Js选择排序的问题。我这段代码,为何实现不了。

python 冒泡排序与快速排序 遇到的错误与问题

jQuery表格排序问题