排序算法

[toc]

常用八大排序算法的Java实现

交换两元素位置的三种方法

交换变量法

public void swap(int[] a,int i,int j){
    int temp = a[i];
    a[i] = a[j];
    a[j] = temp;
}

求和法

弊端在于如果 x 和 y 的数值过大的话,超出 int 的值会损失精度

public void swap(int[] a,int i,int j){
    a[i]=a[i]+a[j];
    a[j]=a[i]-a[j];
    a[i]=a[i]+a[j];
}

位运算法

核心思想:一个数异或同一个数两次,结果还是那个数,而且不会超出int范围

public void swap(int[] a,int i,int j){
    if (a[i] == a[j]) return;
    a[i] ^= a[j];
    a[j] ^= a[i];
    a[i] ^= a[j];
}

冒泡排序

核心思想:从左至右比较相邻的两个元素,将大(小)的移到一端

public void bubSort(int[] a){
    for (int i = 0; i < a.length - 1; i++)
        for (int j = 0; j < a.length - i - 1; j++)
            if (a[j] > a[j + 1])
                swap(a, i, j);
}

快速排序

核心思想:

  1. 选取一个基准(如最左元素)
  2. 将比基准小的和比基准大的交换位置
  3. 将基准放在“中间”,并以此时基准下标为界将数组分为两部分
  4. 将两部分分别递归(分而治之)
/**
 * 入口
 */
public void in() {
    quickSort(a, 0, a.lengyh-1);
}
 
public quickSort(int[] a, int left, int right) {
    if (left > right) return;
    int i = left, j = right;
    while (i != j) {
        while (i < j && a[j] > a[left]) j--;
        while (i < j && a[i] <= a[left]) i++;
        if (i < j) swap(a, i, j);
    }
    swap(a, left, i);
    quickSort(a, left, i - 1);
    quickSort(a, i + 1, right);
}

选择排序

简单选择排序

核心思想:

  1. 获得无序区中的最小(大)值的索引
  2. 将值与当前循环索引值调换
  3. 即,每次选择无序区最大(小)的元素,放到剩余元素队伍的一端
public void simpleSelectSort(int[] a){
    for(int i = 0,len = a.length; i < len; i++){
        int pos = i; // 最小值的索引
        for(int j = i+1; j < len; j++)
            if(a[j] < a[pos])
                pos = j;
        //此时pos是当前的最小值
        swap(a, pos, i);
    }
}

堆排序

堆排序是基于完全二叉树实现的
堆分为大根堆和小根堆

步骤:

  1. 将数组初始化为大(小)根堆
  2. 将无序区的堆顶记录与该区间的最后一个元素交换位置
  3. 将新的无序区调整为大(小)根堆

注意:

  • 堆中任意子树都是堆
  • 做n-1次操作就能够使得文件有序
  • 大根堆排序是递增有序的(每次排序会将最大的值放在无序区的末端),小根堆排序是递减有序的

堆排序树形结构元素变换在线演示


/**
 * 堆排序入口
 */
public void heapSort(int[] a){
    if(a == null || a.length < 2)return;
    //将数组建立为最大堆
    buildMaxHeap(a);
    
    //从最后一个节点开始依次与根节点交换位置
    //并保持无序区依然为大根堆
    for(int i = a.length-1; i >= 0; i--){
        swap(a, 0, i);
        
        // 此时,父节点为根节点,下标为0
        // heapSize为无序区大小,即为i
        max(a, i, 0);
    }
}

/**
 * 初始化堆
 */
public void buildMaxHeap(int[] a){
    for (int i = 0,len = a.length/2; i < len; i++)
        maxHeap(a, a.length, i)
}

/**
 * 调整堆,使得无序区在交换元素后时候依然保持最大堆的性质
 *
 * @param heapSize 二叉树的大小(无序区的长度)
 * @param index    当前节点(父节点)的下标
 */
public void maxHeap(int[] a, int heapSize, int index){
    int left = index * 2 + 1; //左子节点下标 = 父节点下标 * 2 + 1
    int right = index * 2 + 2; //右子节点下标 = 父节点下标 * 2 + 2
    
    // 父节点与其子节点中最大值的下标
    int largest = index;
    
    // 如果左子节点比父节点大
    if(left < heapSize && a[left] > a[largest])
        largest = left; //最大值的下标置为左子节点的下标
        
    // 如果右子节点比父节点和左子节点大
    if(right < heapSize && a[right] > a[largest])
        largest = right;//最大值的下标置为右子节点的下标
    
    // 最大堆的子堆一定是最大堆,所以最大值仍是父节点的话,子树依然是最大堆
    // 如果此时父节点不是最大值,则树结构改变,需要递归使子树也保持最大堆的性质
    if(index != largest){
        swap(a, index, largest);//交换父节点和最大值节点的元素
        // 递归使以原最大值节点为父节点的子树保持最大堆性质
        max(a, heapSize, largest);
    }
}

简便写法

/**
 * 堆排序 (升序)
 * 当堆从数组0开始存储
 * 1)构建大根堆a[0]为大根
 * 2)将大根与末尾元素交换,将a[0,a.len-2]再构建为大根堆
 * 

* 求父节点 parent(i) = (i -1) / 2 * 求左孩子 left child = 2 * i + 1 * 求右孩子 right child = 2 * i + 2 *

* O(nlogn) * * @author Paosin * @version 1.0 * @className HeapSort * @date 2018/2/23 21:22 */ public class HeapSort { public static void main(String[] args) { TestUtils.test(HeapSort.class.getSimpleName(), 20, arr -> heapSort(arr)); } private static void heapSort(Integer[] arr) { // 从第一个非叶子节点开始构建大根堆 for (int i = firstNotLeafNode(arr); i >= 0; i--) { shiftDown(arr, arr.length, i); } // 0不需要再遍历,一定是最小值 // 将大根堆一次把堆顶移到队尾 for (int i = arr.length - 1; i > 0; i--) { // 将堆顶置尾 ArrayUtils.swap(arr, 0, i); // 进行一次构建大根堆 shiftDown(arr, i, 0); } } private static void shiftDown(Integer[] arr, int length, int k) { // 当shiftDown的节点已经是叶子,退出循环 while (2 * k + 1 < length) { int j = leftChild(k); if (j + 1 < length && arr[rightChild(k)] > arr[leftChild(k)]) { j++; } if (arr[k] >= arr[j]) { break; } ArrayUtils.swap(arr, k, j); k = j; } } private static int parent(int k) { return (k - 1) / 2; } private static int leftChild(int k) { return 2 * k + 1; } private static int rightChild(int k) { return 2 * k + 2; } /** * 获取第一个非叶子节点 */ private static int firstNotLeafNode(Integer[] arr) { return (arr.length - 1) / 2; } }

插入排序

直接插入排序

核心思想:

  1. 将待排数组看成两部分:有序区(初始长度为1)和无序区(初始长度为n-1)
  2. 取出无序区的首元素,将其插入有序区适当的位置
    1. 从有序区末端元素开始,依次与无序区首元素(待排元素)比较
    2. 如果此元素比待排元素大(小)则交换位置(冒泡思想)
    3. 依次交换位置直到找到下一个有序区元素比待排元素小(大)
  3. 完成一次排序
//升序
public void straightInsertSort(int[] a){
    // 设有序区初始元素为a[0],所以无序区下标从1开始
    for(int i = 1; i < a.length; i++){
        if(a[i] < a[i - 1]){
            int temp = a[i]; //无序区的待排元素值(无序区首元素),放在临时变量中存储
            int j = i;//待排元素下标,用于寻找正确位置
            
            // 寻找待排元素该呆的位置
            // 找到待排元素前一个节点比它小的位置
            while(j > 0 && a[j - 1] > temp){
                // 如果这个元素比待排元素大,则通过赋值将其右移一位
                a[j] = a[j-- - 1];
            }
            // 将待排元素放置有序区
            a[j] = temp;
        }
    }
}

希尔排序


你可能感兴趣的:(排序算法)