Sort realization (algorithm)

Title: Summary of common sorting algorithm

First, the bubble sort

The core idea: Bubble Sort will only operate two adjacent data. Every bubble will operate on two adjacent elements are compared to see if it meets the size requirements of the relationship. If not, let Talia interchangeable. A bubbling make at least one element is moved to its position should be repeated n times, to complete the work of the n sorted data.

    // bubble sort 
    public  static  void SORT1 ( int [] ARR) {
         // when a particular bubble has been no data exchange operation, description has reached the fully ordered, do not continue to perform the subsequent operation bubbling 
        Boolean In Flag = to false ; // exit the loop before bubbling flag 
        for ( int J = 0; J <arr.length -. 1; J ++) { // control discussed 
            in flag = to false ;
             for ( int I = 0; I <arr.length - . 1 - J; I ++) { // controls the number of comparisons two adjacent numbers 
                IF (ARR [I]> ARR [I +. 1 ]) { 
                    ARR [I] ^ ARR = [I +. 1 ];
                    ARR [I +. 1] ^ = ARR [I]; 
                    ARR [I] ^ ARR = [I +. 1 ]; 
                    In Flag = to true ; // When the data exchange occurs, the flag is set true, the representative round traverse occur switching element 
                } 
            } 
            IF (! In Flag) { // no data exchange, early exit 
                BREAK ; 
            } 
        } 
    }

  

Second, select the sorting method

The core idea: the realization of ideas selection sorting algorithm is somewhat similar to insertion sort, can be divided into intervals sorted and unsorted range. But each time the selection sort sort will never find the smallest element in the interval, place it sorted at the end of the interval.

(Optimized) algorithm:

    // 选择排序
    public static void sort(int[] arr) {
        int i, j, temp;
        for (i = 0; i < arr.length - 1; i++) {// 控制轮数
            // 在每轮中,temp初始存储本轮最小的数的下标
            temp = i;
            for (j = i + 1; j < arr.length; j++) {
                if (arr[j] < arr[temp]) {
                    temp = j;// 保持temp存储的是本轮最小的数的下标
                }
            }

            // 如果temp存储的本轮最小的数的下标与初始值不同,进行数据移动
            if (temp != i) {
                arr[i] ^= arr[temp];
                arr[temp] ^= arr[i];
                arr[i] ^= arr[temp];
            }
        }
    }

(初始)算法:

public static void sort(int[] arr) {// 选择排序
        int i, j, t;
        for (i = 0; i < arr.length - 1; i++) {
            for (j = i + 1; j < arr.length; j++) {
                if (arr[i] > arr[j]) {
                    t = arr[i];
                    arr[i] = arr[j];
                    arr[j] = t;
                }
            }
        }
        System.out.println(Arrays.toString(arr));
    }

 选择排序空间复杂度为 O(1),是一种原地排序算法。选择排序的最好情况时间复杂度、最坏情况和平均情况时间复杂度都为 O(n2)。

选择排序是一种不稳定的排序算法。比如 5,8,5,2,9 这样一组数据,使用选择排序算法来排序的话,第一次找到最小元素 2,与第一个 5 交换位置,那第一个 5 和中间的 5 顺序就变了,所以就不稳定了。正是因此,相对于冒泡排序和插入排序,选择排序就稍微逊色了

 

三、插入排序

 1、基本思想:每步将一个待排序的记录,按其顺序码大小插入到前面已经排序的子序列的合适位置(从后向前找到合适位置后),直到全部插入排序完为止。

 2、实例

         

  3、算法实现

    // 插入排序
    public static void sort2(int[] arr) {
        int temp = 0, j = 0;
        for (int i = 1; i < arr.length; i++) {
            // temp存储待插入数
            temp = arr[i];
            // j存储待插入数的前一个数
            j = i - 1;
            // 查找插入的位置;
            for (; j >= 0; j--) {
                if (arr[j] > temp) {
                    arr[j + 1] = arr[j];// 数据移动
                } else {
                    break;// 当没有数据交换时,说明已经达到完全有序
                }
            }
            arr[j + 1] = temp;// 插入数据
        }
    }

 

四、希尔排序

  1、基本思想:希尔排序也称为“缩小增量排序”,其基本原理是,将待排序的数组元素分成多个子序列,使得每个子序列的元素个数相对较少,然后对各个子序列分别进行直接插入排序,待整个待排序列“基本有序”后,最后再对所有元素进行一次直接插入排序。因此,我们要采用跳跃分割的策略:将相距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。希尔排序是对直接插入排序算法的优化和升级。
所谓的基本有序,就是小的关键字基本在前面,大的基本在后面,不大不小的基本在中间,例如{2,1,3,6,4,7,5,8,9,}就可以称为基本有序了。但像{1,5,9,3,7,8,2,4,6}这样,9在第三位,2在倒数第三位就谈不上基本有序。

  2、复杂度分析:希尔排序的关键并不是随便分组后各自排序,而是将相隔某个“增量”的记录组成一个子序列,实现跳跃式移动,使得排序的效率提高。需要注意的是,增量序列的最后一个增量值必须等于1才行。另外,由于记录是跳跃式的移动,希尔排序并不是一种稳定的排序算法。 希尔排序最好时间复杂度和平均时间复杂度都是O(nlogn),最坏时间复杂度为O(n2)。

  3、希尔排序思想图示:

              

            

           

  4、算法实现

import java.util.Arrays;

public class Test7 {
    public static void shellSortSmallToBig(int[] data) {
        int j = 0;
        int temp = 0;
        for (int increment = data.length / 2; increment > 0; increment /= 2) {
            for (int i = increment; i < data.length; i++) {
                temp = data[i];
                for (j = i - increment; j >= 0; j -= increment) {
                    if (temp < data[j]) {
                        data[j + increment] = data[j];
                    } else {
                        break;
                    }
                }
                data[j + increment] = temp;
            }
        }
    }

    public static void main(String[] args) {
        int[] data = new int[] { 26, 53, 67, 48, 57, 13, 48, 32, 60, 50 };
        shellSortSmallToBig(data);
        System.out.println(Arrays.toString(data));
    }
}
------>[
13, 26, 32, 48, 48, 50, 53, 57, 60, 67]

 

 五、快速排序

快速排序算法(Quicksort)简称为“快排”。快排利用的也是分治思想。

快排的思想是这样的:如果要排序数组中下标从 p 到 r 之间的一组数据,我们选择 p 到 r 之间的任意一个数据作为 pivot(分区点)。

我们遍历 p 到 r 之间的数据,将小于 pivot 的放到左边,将大于 pivot 的放到右边,将 pivot 放到中间。经过这一步骤之后,数组 p 到 r 之间的数据就被分成了三个部分,前面 p 到 q-1 之间都是小于 pivot 的,中间是 pivot,后面的 q+1 到 r 之间是大于 pivot 的。

根据分治、递归的处理思想,我们可以用递归排序下标从 p 到 q-1 之间的数据和下标从 q+1 到 r 之间的数据,直到区间缩小为 1,就说明所有的数据都有序了。

如果我们用递推公式来将上面的过程写出来的话,就是这样:

递推公式:
quick_sort(p…r) = quick_sort(p…q-1) + quick_sort(q+1, r)
 
终止条件:
p >= r

我将递推公式转化成递归代码。跟归并排序一样,我还是用伪代码来实现,你可以翻译成你熟悉的任何语言。

 1 // 快速排序,A 是数组,n 表示数组的大小
 2 quick_sort(A, n) {
 3   quick_sort_c(A, 0, n-1)
 4 }
 5 // 快速排序递归函数,p,r 为下标
 6 quick_sort_c(A, p, r) {
 7   if p >= r then return
 8   
 9   q = partition(A, p, r) // 获取分区点
10   quick_sort_c(A, p, q-1)
11   quick_sort_c(A, q+1, r)
12 }

归并排序中有一个 merge() 合并函数,我们这里有一个 partition() 分区函数。partition() 分区函数实际上我们前面已经讲过了,就是随机选择一个元素作为 pivot(一般情况下,可以选择 p 到 r 区间的最后一个元素),然后对 A[p…r] 分区,函数返回 pivot 的下标。

如果我们不考虑空间消耗的话,partition() 分区函数可以写得非常简单。我们申请两个临时数组 X 和 Y,遍历 A[p…r],将小于 pivot 的元素都拷贝到临时数组 X,将大于 pivot 的元素都拷贝到临时数组 Y,最后再将数组 X 和数组 Y 中数据顺序拷贝到 A[p…r]。

但是,如果按照这种思路实现的话,partition() 函数就需要很多额外的内存空间,所以快排就不是原地排序算法了。如果我们希望快排是原地排序算法,那它的空间复杂度得是 O(1),那 partition() 分区函数就不能占用太多额外的内存空间,我们就需要在 A[p…r] 的原地完成分区操作。

原地分区函数的实现思路非常巧妙,我写成了伪代码,我们一起来看一下。

 1 partition(A, p, r) {
 2   pivot := A[r]
 3   i := p
 4   for j := p to r-1 do {
 5     if A[j] < pivot {
 6       swap A[i] with A[j]
 7       i := i+1
 8     }
 9   }
10   swap A[i] with A[r]
11   return i

这里的处理有点类似选择排序。我们通过游标 i 把 A[p…r-1] 分成两部分。A[p…i-1] 的元素都是小于 pivot 的,我们暂且叫它“已处理区间”,A[i…r-1] 是“未处理区间”。我们每次都从未处理的区间 A[i…r-1] 中取一个元素 A[j],与 pivot 对比,如果小于 pivot,则将其加入到已处理区间的尾部,也就是 A[i] 的位置。

数组的插入操作还记得吗?在数组某个位置插入元素,需要搬移数据,非常耗时。当时我们也讲了一种处理技巧,就是交换,在 O(1) 的时间复杂度内完成插入操作。这里我们也借助这个思想,只需要将 A[i] 与 A[j] 交换,就可以在 O(1) 时间复杂度内将 A[j] 放到下标为 i 的位置。

文字不如图直观,所以我画了一张图来展示分区的整个过程。

因为分区的过程涉及交换操作,如果数组中有两个相同的元素,比如序列 6,8,7,6,3,5,9,4,在经过第一次分区操作之后,两个 6 的相对先后顺序就会改变。所以,快速排序并不是一个稳定的排序算法。

到此,快速排序的原理你应该也掌握了。

 1     /*
 2      * 快速排序
 3      * 
 4      * @param arr 数组
 5      * 
 6      */
 7     public static void quicksort(int[] arr) {
 8         quicksort_c(arr, 0, arr.length - 1);
 9     }
10 
11     /*
12      * 快速排序递归算法
13      * 
14      * @param arr 数组
15      * 
16      * @param p 代表数组的首位下标
17      * 
18      * @param r 代表数组的最后一位下标
19      * 
20      */
21     public static void quicksort_c(int[] arr, int p, int r) {
22         if (p >= r) {// 递归终止的条件
23             return;
24         }
25         int q = partition(arr, p, r);
26         quicksort_c(arr, p, q - 1);
27         quicksort_c(arr, q + 1, r);
28     }
29 
30     /*
31      * 获取分区点
32      * 
33      * @param arr 数组
34      * 
35      * @param p 代表数组的首位下标
36      * 
37      * @param r 代表数组的最后一位下标
38      * 
39      */
40     public static int partition(int[] arr, int p, int r) {
41         int pivot = arr[r];
42         int i = p;
43         int temp = 0;
44         for (int j = p; j <= r - 1; j++) {
45             if (arr[j] < pivot) {
46                 temp = arr[i];
47                 arr[i] = arr[j];
48                 arr[j] = temp;
49                 i++;
50             }
51         }
52         // 交换arr[r]和arr[i]
53         temp = arr[r];
54         arr[r] = arr[i];
55         arr[i] = temp;
56 
57         return i;
58     }

快速排序的性能分析

六、堆排序

堆排序是一种原地的、时间复杂度为 O(nlog⁡n) 的排序算法。

如何理解“堆”?

前面我们提到,堆是一种特殊的树。我们现在就来看看,什么样的树才是堆。我罗列了两点要求,只要满足这两点,它就是一个堆。

  • 堆是一个完全二叉树;

  • 堆中每一个节点的值都必须大于等于(或小于等于)其子树中每个节点的值。

我分别解释一下这两点。

第一点,堆必须是一个完全二叉树。还记得我们之前讲的完全二叉树的定义吗?完全二叉树要求,除了最后一层,其他层的节点个数都是满的,最后一层的节点都靠左排列。

第二点,堆中的每个节点的值必须大于等于(或者小于等于)其子树中每个节点的值。实际上,我们还可以换一种说法,堆中每个节点的值都大于等于(或者小于等于)其左右子节点的值。这两种表述是等价的。

对于每个节点的值都大于等于子树中每个节点值的堆,我们叫作“大顶堆”。对于每个节点的值都小于等于子树中每个节点值的堆,我们叫作“小顶堆”

如何实现一个堆?

我之前讲过,完全二叉树比较适合用数组来存储。用数组来存储完全二叉树是非常节省存储空间的。因为我们不需要存储左右子节点的指针,单纯地通过数组的下标,就可以找到一个节点的左右子节点和父节点。

我画了一个用数组存储堆的例子,你可以先看下。

代码实现:

public class HeapSort {

    public static void main(String[] args) {
        // 如果待排序的组合是(20, 8, 12, 9, 19, 7, 3),为了方面我们加一个0使得arr[i]的下标就是i
        int[] a = { 0, 20, 8, 12, 9, 19, 7, 3 };
        // 第二个参数是: 待排序的数据个数
        heapsort(a, a.length - 1);
        // 输出结果
        for (int i = 1; i < a.length; i++) {
            System.out.print(a[i] + " ");
        }
    }

    /**
     * 堆排序
     *
     * @param arr
     *            数组
     * @param n
     *            待排序的数据个数
     */
    public static void heapsort(int[] arr, int n) {
        // 1、建堆
        buildHeap(arr, n);
        // 2、排序
        int k = n;
        while (k > 1) {
            swap(arr, 1, k);
            k--;
            heapify(arr, k, 1);
        }
    }

    /**
     * 建堆
     * 
     * @param arr
     *            数组
     * @param n
     *            待排序的数据个数
     * 
     */
    public static void buildHeap(int[] arr, int n) {
        for (int i = n / 2; i >= 1; i--) {
            heapify(arr, n, i);
        }
    }

    /**
     * 自上往下堆化(将传入的堆顶元素arr[i]放至其正确的位置)
     * 
     * @param arr
     *            数组
     * @param n
     *            待排序的数据个数
     * @param i
     *            堆顶元素下标
     * @return 无返回值
     * 
     */
    public static void heapify(int[] arr, int n, int i) {
        int maxpos = 0;
        while (true) {
            maxpos = i;
            if (2 * i <= n && arr[2 * i] > arr[i]) {
                maxpos = 2 * i;
            }
            if (2 * i + 1 <= n && arr[2 * i + 1] > arr[maxpos]) {
                maxpos = 2 * i + 1;
            }
            if (maxpos == i) {
                break;
            }
            swap(arr, i, maxpos);
            i = maxpos;

        }
    }

    /**
     * 交换数组中指定的两个元素值
     * 
     * @param arr
     *            数组
     * @param x
     *            数组待交换值下标1
     * @param y
     *            数组待交换值下标2
     * @return 无返回值
     */
    public static void swap(int[] arr, int x, int y) {
        int t = arr[x];
        arr[x] = arr[y];
        arr[y] = t;
    }

}

输出:

3 7 8 9 12 19 20 

 

Guess you like

Origin www.cnblogs.com/tubeWang/p/10027224.html