算法学习笔记(三):归并排序、基数排序、堆排序
归并排序
归并排序是利用归并思想,先将数组拆分成一个又一个小元素,再对其进行“治”,即将其排序,最后合成一个有序的序列。如图所示:
“治”阶段以最后一次“治”阶段为例分析,我们需要将两个已经有序的子序列合并成一个有序序列,步骤如下:
可以看到,大致步骤分为三步:
1. 对比左右两部分的各位数字,将较小的数字放入临时数组;
2. 在一方全部放完之后,将另一方剩余的元素(已排序)按序放入临时数组;
3. 将临时数组中的元素拷贝进原数组。
“分”阶段则与之前的快排相似。找出中间数,再对两边进行递归操作,直到每一个区间都只剩一个元素,该元素就是该区间的中间数mid。
实现代码如下:
public class MargeSort {
//分+合操作
public void margeSort(int[] arr,int left,int right,int[] temp){
if (left < right) {
int mid = (left + right) / 2;
margeSort(arr, left, mid, temp);
margeSort(arr, mid+1, right, temp);
marge(arr,left,mid,right,temp);
}
}
//合操作
public void marge(int[] arr,int left,int mid,int right,int[] temp){
int i = left;
int j = mid + 1;
int t = 0;
//此时传进来的数组左右半部分都各是有序数组
//第一步:将元素按大小放进临时数组temp
while (i <= mid && j <= right){
if (arr[i] >= arr[j]){
temp[t] = arr[j];
j++;
t++;
}else {
temp[t] = arr[i];
i++;
t++;
}
}
//第二步:此时有一边的元素全部进入临时数组,故此时需要将另一边的元素按序放入临时数组
while (i <= mid){ //左半边元素有剩余
temp[t] = arr[i];
t++;
i++;
}
while (j <= right){
temp[t] = arr[j];
j++;
t++;
}
//第三步:将临时数组里面的元素拷贝到原数组中
t = 0;
int templeft = left;
while (templeft <= right){
arr[templeft] = temp[t];
templeft++;
t++;
}
}
}
基数排序
计数排序是通过键值的各个位的值,将要排序的元素分配到某些“桶”中,达到排序的效果。属于稳定性排序。
基本思想是将所有待比较的数值统一为同样的数位长度,数位较短的数前面补零。然后从最低位开始,一次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
图文说明:
- 第一轮:创建十个桶,将数字按照个位依次填入;
- 第二轮:将数字按照十位的数字依次填入桶中;
- 第三轮:将数组按照百位依次填入桶中,完成排序。
实现代码:
public class CardinalitySort {
public void cadinalitySort(int[] arr){
//首先创建十个桶,用二维数组统一管理
int[][] tempArr = new int[10][arr.length];
//创建一个临时数组,用以计量各个桶中放入的数字的数量
int[] counts = new int[10];
//得到最大值
int max = getMax(arr);
//由最大值得到最大值的位数
int len = String.valueOf(max).length();
//n为每一轮要获取的对应的位
for (int i = 0,n = 1; i < len; i++,n*=10) {
//将数字依次放入对应的桶中
for (int j = 0; j < arr.length; j++) {
int bit = arr[j]/n%10;
tempArr[bit][counts[bit]] = arr[j];
counts[bit]++;
}
//将放好的数字取出,放进原数组
int index = 0;
for (int k = 0; k < 10; k++) {
//当该桶中有数字时,进行取出
if (counts[k] != 0){
for (int h = 0; h < counts[k]; h++) {
arr[index] = tempArr[k][h];
index++;
}
counts[k] = 0;
}
}
}
}
public int getMax(int[] arr){
int maxIndex= 0;
for (int i = 1; i < arr.length; i++) {
if (arr[i-1] >= arr[i]){
maxIndex = i-1;
}else {
maxIndex = i;
}
}
return arr[maxIndex];
}
}
堆排序
利用堆结构设计而成的一种排序算法,堆排序是一种选择排序。
首先了解两个概念:大顶堆和小顶堆。如图:
大顶堆:所有根节点都一定比子节点大;
定义为: arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:所有根节点都一定比子节点小;
定义为: arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
接着再看堆排序的基本思想:
- 将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点;
- 将其末尾元素进行交换,此时末尾就称为最大值;
- 然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值;
- 如此反复执行,就能得到一个有序序列了。
实现代码:
public class HeapSort {
public void heapSort(int[] arr){
int temp = 0;
//每次大顶堆排序完后,将根节点(最大值)和末尾节点(最小值)进行调换
//每次调换完后去掉末尾结点,树的长度-1,对剩余的树继续大顶堆排序
for (int i = arr.length; i > 0; i--) {
//里层for循环改变每次进行大顶堆排序的非叶子节点
for (int j = arr.length/2 -1 ; j >= 0 ; j--) {
toMaxHeap(arr,i,j);
}
if (arr[i-1] >= 0){
temp = arr[i-1];
arr[i-1] = arr[0];
arr[0] = temp;
}
}
}
//对以index节点为非叶子节点的子树进行大顶堆排序
public void toMaxHeap(int[] arr,int size,int index){
int left = index * 2 +1;
int right = index * 2 +2;
int maxindex = index;
if (left < size && arr[left] > arr[maxindex]){
maxindex = left;
}
if (right < size && arr[right] > arr[maxindex]){
maxindex = right;
}
if (index != maxindex){
int temp = arr[index];
arr[index] = arr[maxindex];
arr[maxindex] = temp;
//因此调换后子树可能不符合大顶堆排序,故递归进行大顶堆排序
toMaxHeap(arr,size,maxindex);
}
}
}