归并排序
算法思想:归并排序采用分治的思想,先将数组进行拆分成一个个子序列,直到拆到只剩一个元素,然后再进行有序合并,最后合并成一个有序的数组。
时间复杂度:使用分治递归的思想,T(n) = O(nlogn)
// 归并排序
public static void MergeSort(SortArray arr,int low, int high){
if(low>=high) return;
int mid = (low+high)/2;
// 递归归并
MergeSort(arr,low,mid);
MergeSort(arr,mid+1,high);
int i = 0, j = low, k =mid+1;
int [] temp = new int[100];
//有序合并
while(j<=mid && k<=high){
if(arr.getNum(j)<arr.getNum(k)) temp[i++] = arr.getNum(j++);
else temp[i++] = arr.getNum(k++);
}
// 多余的元素直接添加到数组末尾
while(j<=mid) temp[i++] = arr.getNum(j++);
while(k<=high) temp[i++] = arr.getNum(k++);
// 将有序的临时数组返回到原数组
for (i = low,j = 0; i <= high ; i++,j++) {
arr.changeValue(i,temp[j]);
}
}
计数排序
算法思想:前面用到的排序算法都是基于比较思想,计数排序很巧妙,并不进行比较,更像提取出数组的特征,然后根据得到的特征数组获得排序后的数组。在一些特殊的应用场景(数据集中)算法效率很高。随着时间复杂度的降低,空间复杂度显著提高,但是,使用空间复杂度换取时间复杂度是值得的,而且算法的具体实现中还有很多小技巧以减少多余空间的使用。(从算法思想中可看出,计数排序并不适用于浮点数的排序)
时间复杂度:时间复杂度为,T(n) = O(n+k),k为特征数组的长度,这里就是(max-min+1)
// 计数排序
public static void CountSort(SortArray arr){
int min = 101,max = -1;
// 找到最大值和最小值
for (int i = 0; i < arr.getLen(); i++) {
if(arr.getNum(i) < min) min = arr.getNum(i);
else if(arr.getNum(i)>max) max = arr.getNum(i);
}
int[] temp = new int[max-min+1]; // 减小多余空间使用,后面得数以min为基准
for (int i = 0; i < arr.getLen(); i++) {
temp[arr.getNum(i)-min] ++;
}
int j = 0;
for (int i = 0; i < temp.length; i++) {
while(temp[i]>0){
arr.changeValue(j++,i+min);
temp[i]--;
}
}
}
桶排序
算法思想:与计数排序类似,但是又不同的地方,该算法是将数据先分为一个一个组(桶),在组(桶)内再进行排序(使用其他排序算法),然后依次将几个组(桶)内数据取出即可完成排序,桶排序适用于数据分布均匀的情况,这样各组数据分布也均匀,算法效率最高。(桶排序可以应用于浮点数排序)
时间复杂度:在最理想的情况(数据均匀分布)下,T(n) 接近 O(n)
代码实现并不复杂,主要就是将数据分组,在组内使用其他排序算法(一般使用插入排序)进行排序,自己编写试试吧!
基数排序
算法思想:想一下我们平时是怎么比较数的大小的?先从最高位开始比较,最高位最大的一定最大,然后依次往下比较。计数排序就是这种思想,并且,基数排序有两种,分别是从高到低比较(MSD)和从低到高(LSD)比较。(这里介绍LSD)
时间复杂度:两重循环,T(n) = O( k*n ) ,k为数据的最大数据宽度(位数)。
public static void RadixSort(SortArray arr,int dataWidth){
int n = 1; // 数据宽度记录位,记录当前的排序是第几位
int length = arr.getLen();
int[][] bucket = new int[10][length]; // 二维数组,存储每次数据的分组结果
int[] order = new int[10]; // 一维数组,记录0-9每组的数据元素多少
while(n < dataWidth){
// 将所有元素按照当前循环的位数,放到对应的桶中
for (int i = 0; i < arr.getLen(); i++) {
int temp = (arr.getNum(i)/n)%10; // 取出对应位数
bucket[temp][order[temp]++] = arr.getNum(i);
}
// 将桶中数据覆盖保存到原数组
int k = 0;
for(int i=0; i<10 ;i++) {
for (int j = 0; j < order[i]; j++) { // 一定要从小到大遍历,这样才不会破坏以前的排序结果
arr.changeValue(k++, bucket[i][j]);
}
order[i] = 0; // order置零
}
n *= 10; // 位数*10,用于下一次排序
}
}
堆排序
算法思想:堆排序使用到了堆这种数据结构(建议先去看看),而堆基于完全二叉树。算法首先构建一个大顶堆,然后循环,由于堆顶元素最大,每次将堆顶元素取出,将堆尾元素放到堆顶,重新构建大顶堆…直到堆的元素只剩一个,排序完成。
算法时间复杂度:堆排序的时间复杂度主要就是建堆所用。所以T(n) = O(nlogn)
public static void HeapSort(SortArray arr) {
for (int i = (arr.getLen() - 1) / 2; i >= 0; i--) {
//从第一个非叶子结点从下至上,从右至左调整结构
generateHeap(arr, i, arr.getLen());
}
//调整堆结构+交换堆顶元素与末尾元素
for (int i = arr.getLen() - 1; i > 0; i--) {
//将堆顶元素与末尾元素进行交换
arr.swap(0,i);
//重新对堆进行调整
generateHeap(arr, 0, i);
}
}
// 生成堆
public static void generateHeap(SortArray arr,int parent,int length){
//将temp作为父节点
int temp = arr.getNum(parent);
//左孩子
int lChild = 2 * parent + 1;
while (lChild < length) {
//右孩子
int rChild = lChild + 1;
// 如果有右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点
if (rChild < length && arr.getNum(lChild) < arr.getNum(rChild)) {
lChild++;
}
// 如果父结点的值已经大于孩子结点的值,则直接结束
if (temp >= arr.getNum(lChild)) {
break;
}
// 把孩子结点的值赋给父结点
arr.changeValue(parent,arr.getNum(lChild));
//选取孩子结点的左孩子结点,继续向下筛选
parent = lChild;
lChild = 2 * lChild + 1;
}
arr.changeValue(parent,temp);
}
完结
这一篇介绍的五种算法相较于上一篇要难一些,使用条件也较为苛刻,大家好好理解一下。