归并排序、堆排序、快速排序


归并排序

最好情况:Ο(nlogn)
最坏情况:Ο(nlogn)
平均情况:Ο(nlogn)
辅助空间:Ο(n)
稳定性:稳定

归并排序是创建在归并操作上的一种有效的排序算法,效率为O(nlogn),1945年由冯·诺伊曼首次提出。
归并排序的实现分为递归实现与非递归(迭代)实现。递归实现的归并排序是算法设计中分治策略的典型应用,我们将一个大问题分割成小问题分别解决,然后用所有小问题的答案来解决整个大问题。非递归(迭代)实现的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。

递归法

过程:

  1. 计算左右边界和中间分割线
  2. 递归左右数组
  3. 将左右数组合并
    3.1. 提取左右数组为左右临时数组
    3.2. 在左右临时数组提取较大(小)的元素放入原数组
public static void main(String[] args) {
    int[] arr = new int[]{5, 8, 6, 12, 2, 4, 10, 9, 1, 7, 3, 13, 0};
    int length1 = arr.length;
    leftArr = new int[length];
    rightArr = new int[length];
    mergeSortRecursion(arr, 0, length - 1);
}

static void mergeSortRecursion(int[] arr, int left, int right) {    //递归实现的归并排序
    int middle = (left + right) / 2;    //取中间位置
    if (left < right) {
        mergeSortRecursion(arr, left, middle);    //递归左边数组
        mergeSortRecursion(arr, middle + 1, right);    //递归右边数组
        merge(arr, left, middle, right);    //将已经排序的左边数组和右边数组进行排序
        print(arr);
    }
}

static void merge(int[] arr, int left, int middle, int right) {
    int[] leftArr = new int[right - left + 1];
    int[] rightArr = new int[right - left + 1];
    System.arraycopy(arr, left, leftArr, 0, middle - left + 1);    //将左边数组拷贝到leftArr
    System.arraycopy(arr, middle + 1, rightArr, 0, right - middle);    //将右边数组拷贝到rightArr
    leftArr[middle - left + 1] = Integer.MAX_VALUE;    //在数组最后面设置正无穷,使下面循环内的判断不会越界
    rightArr[right - middle] = Integer.MAX_VALUE;
    for (int k = left, i = 0, j = 0; k <= right; k++) {
        if (leftArr[i] <= rightArr[j]) {
            arr[k] = leftArr[i++];
        } else {
            arr[k] = rightArr[j++];
        }
    }
}

输出:

5 8 6 12 2 4 10 9 1 7 3 13 0
5 8 6 12 2 4 10 9 1 7 3 13 0
5 6 8 12 2 4 10 9 1 7 3 13 0
5 6 8 12 2 4 10 9 1 7 3 13 0
5 6 8 12 2 4 10 9 1 7 3 13 0
2 4 5 6 8 10 12 9 1 7 3 13 0
2 4 5 6 8 10 12 1 9 7 3 13 0
2 4 5 6 8 10 12 1 7 9 3 13 0
2 4 5 6 8 10 12 1 7 9 3 13 0
2 4 5 6 8 10 12 1 7 9 0 3 13
2 4 5 6 8 10 12 0 1 3 7 9 13
0 1 2 3 4 5 6 7 8 9 10 12 13

迭代法

过程:

  1. 计算步长,步长每次*2
  2. 根据步长计算左右边界和中间分割线
  3. 将左右数组合并
  4. 重新计算左右边界
static void mergeSortIteration(int[] arr, int left, int right) {    //迭代实现的归并排序
    for (int gap = 1; gap <= right - left; gap *= 2) {
        int leftPoint = left, rightPoint;    //left和right都是数组的左右边界,leftPoint和rightPoint都是代表数组位置的游离指针;
        while (leftPoint + gap <= right) {
            rightPoint = leftPoint + gap * 2 - 1;
            int middle = (leftPoint + rightPoint) / 2;    //middle一定要放在检查rightPoint的前面,这样才能准确分块
            if (rightPoint > right) {
                rightPoint = right;
            }
            merge(arr, leftPoint, middle, rightPoint);
            leftPoint = rightPoint + 1;
            print(arr);
        }
    }
}

输出:

5 8 6 12 2 4 10 9 1 7 3 13 0
5 8 6 12 2 4 10 9 1 7 3 13 0
5 8 6 12 2 4 10 9 1 7 3 13 0
5 8 6 12 2 4 9 10 1 7 3 13 0
5 8 6 12 2 4 9 10 1 7 3 13 0
5 8 6 12 2 4 9 10 1 7 3 13 0
5 6 8 12 2 4 9 10 1 7 3 13 0
5 6 8 12 2 4 9 10 1 7 3 13 0
5 6 8 12 2 4 9 10 1 3 7 13 0
2 4 5 6 8 9 10 12 1 3 7 13 0
2 4 5 6 8 9 10 12 0 1 3 7 13
0 1 2 3 4 5 6 7 8 9 10 12 13

归并排序

归并排序


堆排序

最好情况:Ο(nlogn)
最坏情况:Ο(nlogn)
平均情况:Ο(nlogn)
辅助空间:Ο(1)
稳定性:不稳定

堆排序是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构(通常堆是通过一维数组来实现的),并同时满足堆的性质:即子结点的键值总是小于(或者大于)它的父节点。

过程:

  1. 创建初始堆(最大堆或最小堆)
    1.1. 调整每个非叶子节点及其子堆
  2. 根节点与最后元素交换
    2.1. 逐渐缩小堆
    2.2. 从堆顶进行堆调整
public static void main(String[] args) {
    int[] arr = new int[]{5, 8, 6, 12, 2, 4, 10, 9, 1, 7, 3, 13, 0, 11};
    sortHeap(arr);
}

static void sortHeap(int[] arr) {
    buildHeap(arr);
    for (int i = arr.length - 1; i >= 1; i--) {      //对最大堆进行排序
        exchange(arr, 0, i);      //将第根节点元素与最后节点(i)元素交换
        heapAdjust(arr, 0, i);    //最后节点元素被固定,即可交换的数组长度为i
        print(arr);
    }
}

static void buildHeap(int[] arr) {    //建堆(最大堆)
    for (int i = (arr.length - 1) / 2; i >= 0; i--) {
        heapAdjust(arr, i, arr.length);    //自下向上对每个非叶子节点进行调整
        print(arr);
    }
}

static void heapAdjust(int[] arr, int i, int heapSize) {
    int leftChild = i * 2 + 1;    //i节点(父节点)的左孩子和右孩子
    int rightChild = leftChild + 1;
    int largest = i;    //父节点、左孩子、右孩子进行大小比较
    if (leftChild < heapSize && arr[leftChild] > arr[i]) {
        largest = leftChild;
    }
    if (rightChild < heapSize && arr[rightChild] > arr[leftChild]) {
        largest = rightChild;
    }
    if (largest != i) {    //如果最大值不是父节点,那么不符合堆性质,调整最大值位置
        exchange(arr, i, largest);
        heapAdjust(arr, largest, heapSize);    //从被调整的孩子继续递归
    }
}

static void exchange(int[] arr, int a, int b) {
    int temp = arr[a];
    arr[a] = arr[b];
    arr[b] = temp;
}

static void print(int[] arr) {
    for (int a : arr) {  //输出
        System.out.print(a + "\t");
    }
    System.out.println();
}

输出:

5 8 6 12 2 4 11 9 1 7 3 13 0 10
5 8 6 12 2 13 11 9 1 7 3 4 0 10
5 8 6 12 7 13 11 9 1 2 3 4 0 10
5 8 6 12 7 13 11 9 1 2 3 4 0 10
5 8 13 12 7 6 11 9 1 2 3 4 0 10
5 12 13 9 7 6 11 8 1 2 3 4 0 10
13 12 11 9 7 6 10 8 1 2 3 4 0 5
12 9 11 8 7 6 10 5 1 2 3 4 0 13
11 9 10 8 7 6 0 5 1 2 3 4 12 13
10 9 6 8 7 4 0 5 1 2 3 11 12 13
9 8 6 5 7 4 0 3 1 2 10 11 12 13
8 7 6 5 2 4 0 3 1 9 10 11 12 13
7 5 6 3 2 4 0 1 8 9 10 11 12 13
6 5 4 3 2 1 0 7 8 9 10 11 12 13
5 3 4 0 2 1 6 7 8 9 10 11 12 13
4 3 1 0 2 5 6 7 8 9 10 11 12 13
3 2 1 0 4 5 6 7 8 9 10 11 12 13
2 0 1 3 4 5 6 7 8 9 10 11 12 13
1 0 2 3 4 5 6 7 8 9 10 11 12 13
0 1 2 3 4 5 6 7 8 9 10 11 12 13

堆排序

堆排序是不稳定的排序算法,不稳定发生在堆顶元素与A[i]交换的时刻。


快速排序

最好情况:Ο(nlogn)
最坏情况:Ο(n2
平均情况:Ο(nlogn)
辅助空间:Ο(logn)~ Ο(n)
稳定性:不稳定

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序n个元素要O(nlogn)次比较。在最坏状况下则需要O(n^2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他O(nlogn)算法更快,因为它的内部循环可以在大部分的架构上很有效率地被实现出来。
快速排序使用分治策略(Divide and Conquer)来把一个序列分为两个子序列。

过程:

  1. 按已坐好位置的基准将数组分为左右两个子数组
  2. 将比基准小的元素放到前面,比基准大的元素放到后面
  3. 将基准放置到准确位置
public static void main(String[] args) {
    int[] arr = new int[]{5, 8, 6, 12, 2, 4, 10, 9, 1, 7, 3, 13, 0, 11};
    quickSort(arr, 0, arr.length - 1);
}

static void quickSort(int[] arr, int left, int right) {    //递归分配基准的左右数组
    if (left < right) {
        int index = partition(arr, left, right);
        quickSort(arr, left, index - 1);
        quickSort(arr, index + 1, right);
    }
}

static int partition(int[] arr, int left, int right) {    //比基准小的放到基准左边,比基准大的放到基准右边
    int pivot = arr[right];    //基准值,不一定是最右侧的元素
    int lastArrTail = left;    //被排序数列最左侧的游离指针,指针所在位置及往前位置的元素,皆比基准小
    for (int i = left; i < right; i++) {    //将比基准小的元素与比基准大的元素进行交换
        if (arr[i] <= pivot) {
            exchange(arr, i, lastArrTail++);
        }
    }
    exchange(arr, right, lastArrTail);    //将基准放在游离指针的位置,该位置及往后的元素比基准大,该位置往前的元素比基准小
    print(arr);
    return lastArrTail;    //排布完成后,返回该基准的位置,该基准已经坐在正确的排列位置上了,但基准的前后序列可能还存在乱序
}

输出:

5 8 6 2 4 10 9 1 7 3 0 11 12 13
0 8 6 2 4 10 9 1 7 3 5 11 12 13
0 2 4 1 3 5 9 8 7 6 10 11 12 13
0 2 1 3 4 5 9 8 7 6 10 11 12 13
0 1 2 3 4 5 9 8 7 6 10 11 12 13
0 1 2 3 4 5 9 8 7 6 10 11 12 13
0 1 2 3 4 5 6 8 7 9 10 11 12 13
0 1 2 3 4 5 6 8 7 9 10 11 12 13
0 1 2 3 4 5 6 7 8 9 10 11 12 13
0 1 2 3 4 5 6 7 8 9 10 11 12 13

快速排序

快速排序是不稳定的排序算法,不稳定发生在基准元素与A[tail+1]交换的时刻。






来源:http://www.cnblogs.com/eniac12/p/5329396.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值