冒泡排序
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个,小的数往上冒。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
public static void bubbleSort(int[] numbers)
{
int temp = 0;
int size = numbers.length;
for(int i = 0 ; i < size-1; i ++)
{
//一趟比较之后,最大的元素就到了最右边(最下面)
for(int j = 0 ;j < size-1-i ; j++)
{
//小数往上冒,大数下沉
if(numbers[j] > numbers[j+1])
{
temp = numbers[j];
numbers[j] = numbers[j+1];
numbers[j+1] = temp;
}
}
}
}
快速排序
public static void quickSort(int[] arr, int low, int high) {
int i, j, temp;
if (low > high) {
return;
}
i = low;//左边哨兵的索引
j = high;//右边哨兵的索引
temp = arr[low];//以最左边为基准位
while (i < j) {
//先看右边,依次往左递减
//先从右往左找一个小于 基准位的数
//当右边的哨兵位置所在的数>基准位的数时
//继续从右往左找(同时 j 索引-1)
//找到后会跳出while循环
while (temp <= arr[j] && i < j) {
j--;
}
//再看左边,依次往右递增,步骤和上面类似
while (temp >= arr[i] && i < j) {
i++;
}
//如果满足条件则交换
if (i < j) {
// 左右哨兵 交换数据(互相持有对方的数据)
arr[i] = arr[i]^arr[j];
arr[j] = arr[i]^arr[j];
arr[i] = arr[i]^arr[j];
}
}
//这时 跳出了 “while (i<j) {}” 循环,说明 i=j 左右在同一位置
//最后将基准为与i和j相等位置的数字交换
arr[low] = arr[i];//或 arr[low] = arr[j];
arr[i] = temp;//或 arr[j] = temp;
//递归调用左半数组
quickSort(arr, low, j - 1);
//递归调用右半数组
quickSort(arr, j + 1, high);
}
直接选择排序
- 在要排序的一组数中,选出最小的一个数与第一个位置的数交换;
- 然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环直到只剩两个数做最后一次排序。
public static void selectSort(int[] numbers)
{
for(int i = 0 ; i < numbers.length -1 ; i++)
{
int k = i;
//选择出应该在第i个位置的数
for(int j = numbers.length -1 ; j > i ; j--)
{
if(numbers[j] < numbers[k]) k = j;
}
//交换两个数
numbers[i] = numbers[i]^numbers[k];
numbers[k] = numbers[i]^numbers[k];
numbers[i] = numbers[i]^numbers[k];
}
}
直接插入排序
- 选出最左边两个元素,排序,得到一个排序好的子集
- 选出左边的第三个元素,插入到排序好的子集当中(从右到左遍历,找到合适的插入位置)
- 循环执行第二部,直到最后一个元素。
public static void insertSort(int[] numbers)
{
int temp,j;
for(int i = 0 ; i < numbers.length ; i++)
{
temp = numbers[i];
//假如temp比前面的值小,则将前面的值后移
for(j = i ; j > 0 && temp < numbers[j-1] ; j --)
{
numbers[j] = numbers[j-1];
}
numbers[j] = temp;
}
}
希尔排序(直接插入改良版)
插入排序当数组基本有序时,比较高效。但是对于较大规模且无序的数据,插入排序效率就低了。可以采用希尔排序。
可以看出,他是按下标相隔距离为4分的组,也就是说把下标相差4的分到一组,比如这个例子中a[0]与a[4]是一组、a[1]与a[5]是一组…,这里的差值(距离)被称为增量。
每个分组进行插入排序后,各个分组就变成了有序的了(整体不一定有序)。
然后缩小增量为上个增量的一半:2,继续划分分组,此时,每个分组元素个数多了,但是,数组变的部分有序了,插入排序效率同样比高。
同理对每个分组进行排序(插入排序),使其每个分组各自有序。
最后设置增量为上一个增量的一半:1,则整个数组被分为一组,此时,整个数组已经接近有序了,插入排序效率高。
对以上数组再进行插入排序后,希尔排序就全部完成了。
有一点需要特别补充,对各个组进行插入排序的时候,并不是先对一个组进行排序完,再对另一个组排序。而是每个组并发执行的,每次插入一个元素进行排序。
public static void shellSort(int [] arr){
int N = arr.length;
//进行分组,最开始的时候,增量为数组的一半,即分成N/2组
for(int gap=N/2;gap>0;gap/=2){
/**
* 对每组进行插入排序
* 对各个组进行插入排序的时候,
* 并不是先对一个组进行排序完,再对另一个组排序
* 而是每个组并发,每次插入一个元素进行排序
*/
for(int i = gap; i<N; i++){
//将arr[i]插入到分组的正确位置上
int insert = arr[i];
int idx;
for(idx = i-gap; idx >=0 && insert<arr[idx]; idx-=gap){
arr[idx+gap] = arr[idx];
}
arr[idx+gap] = insert;
}
}
}
归并排序
归并(Merge)排序法是将两个有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
合并方法:
设r[i…n]由两个有序子表r[i…m]和r[m+1…n]组成,两个子表长度分别为n-i +1、n-m。
1、j=m+1;k=i;i=i; //置两个子表的起始下标及辅助数组的起始下标
2、若i>m 或j>n,转⑷ //其中一个子表已合并完,比较选取结束
3、//选取r[i]和r[j]较小的存入辅助数组rf
如果r[i]<r[j],rf[k]=r[i]; i++; k++; 转⑵
否则,rf[k]=r[j]; j++; k++; 转⑵
4、//将尚未处理完的子表中元素存入rf
如果i<=m,将r[i…m]存入rf[k…n] //前一子表非空
如果j<=n , 将r[j…n] 存入rf[k…n] //后一子表非空
5、合并结束。
代码实现
public static int[] mergeSort(int[] nums, int low, int high) {
int mid = (low + high) / 2;
if (low < high) {
// 左边
mergeSort(nums, low, mid);
// 右边
mergeSort(nums, mid + 1, high);
// 左右归并
{
int[] temp = new int[high - low + 1];
int i = low;// 左指针
int j = mid + 1;// 右指针
int k = 0;
// 把较小的数先移到新数组中
while (i <= mid && j <= high) {
if (nums[i] < nums[j]) {
temp[k++] = nums[i++];
} else {
temp[k++] = nums[j++];
}
}
// 把左边剩余的数移入数组
while (i <= mid) {
temp[k++] = nums[i++];
}
// 把右边边剩余的数移入数组
while (j <= high) {
temp[k++] = nums[j++];
}
// 把新数组中的数覆盖nums数组
for (int k2 = 0; k2 < temp.length; k2++) {
nums[k2 + low] = temp[k2];
}
}
}
return nums;
}
堆排序(直接选择改良版)
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
详细介绍可以参考下面这篇文章
https://www.cnblogs.com/chengxiao/p/6129630.html
堆排序是一种选择排序,概括如下:
- 构建初始堆(大顶堆或小顶堆)
- 交换堆顶元素和末尾元素(选择出了最大或最小元素)
- 剔除末尾元素,回到第一步重建堆的操作,如此循环直到结束。
其中构建初始堆经推导复杂度为O(n),在交换并重建堆的过程中,需交换n-1次,而重建堆的过程中,根据完全二叉树的性质,[log2(n-1),log2(n-2)…1]逐步递减,近似为nlogn。所以堆排序时间复杂度一般认为就是O(nlogn)级。
代码实现:
public static void hepSort(int []arr){
//1.构建大顶堆
for(int i=arr.length/2-1;i>=0;i--){
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(arr,i,arr.length);
}
//2.调整堆结构+交换堆顶元素与末尾元素
for(int j=arr.length-1;j>0;j--){
//将堆顶元素与末尾元素进行交换
int temp=arr[0];
arr[0] = arr[j];
arr[j] = temp;
//重新对堆进行调整
adjustHeap(arr,0,j);
}
}
/**
* 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
* @param arr
* @param i
* @param length
*/
private static void adjustHeap(int []arr,int i,int length){
int temp = arr[i];//先取出当前元素i
for(int k=i*2+1;k<length;k=k*2+1){//从i结点的左子结点开始,也就是2i+1处开始
if(k+1<length && arr[k]<arr[k+1]){//如果左子结点小于右子结点,k指向右子结点
k++;
}
if(arr[k] >temp){//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
arr[i] = arr[k];
i = k;
}else{
break;
}
}
arr[i] = temp;//将temp值放到最终的位置
}
基数排序
基数排序法是属于稳定性的排序,其时间复杂度为O (nlog®m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。
1.先按照个位数的数值,遍历数组,把它们分配至0到9的桶中。
2.将桶中的值重新串联起来
3.按照十位数的数值,遍历2串联起来的数组,把它们分配到0到9的桶中。
4.将桶中的值重新串联起来
5.如果待排序数组中有百位,千位数值,则重复3,4步操作,直至最高位排序完。
https://www.runoob.com/w3cnote/radix-sort.html
总结
希尔排序是选择排序的改良版,堆排序是快速排序的改良版。
这四种排序都属于不稳定排序。