1. 插入排序
1.1 直接插入排序
1.2 希尔排序
2. 选择排序
2.1 简单选择排序
2.2 堆排序
3. 交换排序
3.1 冒泡排序
3.2 快速排序
4. 归并排序
5. 基数排序
希尔排序,堆排序,基数排序个人觉得比较难,本文中只涉及概念,笔记单独写
1. 插入排序
1.1 直接插入排序
基本思想
将数组分为有序与无序两部分,初始时,有序部分只包含第一个元素,其后元素视为无序数组,取出无序数组的第一个元素,插入到有序数组的相应位置。
排序过程
以数列{3,5,4,2,1}为例 : 】前的就是有序数列
初始,将数组分为有序与无序两部分 3 】 5 4 2 1
第一次:无序数组第一位为5,比3大,插入到3之后 3 5 】 4 2 1
第二次:无序数组第一位为4,比3大,比5小,插入之间 3 4 5 】 2 1
第三次:无需数组第一位为2,比3小,插入到3之前 2 3 4 5 】 1
第四次:无序数组第一位为1,比2小,插入到2之前 1 2 3 4 5 】
排序完成,最终顺序{1,2,3,4,5}
代码实现
public static int[] sort(int[] arr){
int j = 0;
//arr[0]视为有序数组,所以外层循环从1开始
for (int i = 1; i < arr.length; i++) {
int insert = arr[i];//要插的数据
for (j = i; j > 0 && insert < arr[j-1]; j--) {
//从后往前插,大的数依次后移
arr[j] = arr[j-1];
}
arr[j] = insert;
}
return arr;
}
1.2 希尔排序
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
1)插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
2)插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位
2. 选择排序
2.1 简单选择排序
简单选择排序,又叫直接选择排序。
基本思想
每一趟从待排序的数据元素中选择出最小(或最大)的一个元素,顺序放在已排好的数列后,直到全部待排序数据元素排完。
排序过程
以数列{4,3,5,2,1}为例 : 】前的就是已经排序的有序数列
第一次:最小值为1,与第一个交换 1 】 4 3 5 2
第二次:最小值为2,与第二个交换 1 2 】 4 3 5
第三次:最小值为3,与第三个交换 1 2 3 】 4 5
第四次:最小值为4,无需交换 1 2 3 4 】 5
(最后一位已经确定最大,无需下一次排序,所以交换次数是arr.length-1)
排序完成,最终顺序{1,2,3,4,5}
代码实现
static int[] sort(int[] arr){
int minIndex;
for (int i = 0; i < arr.length-1; i++) {
minIndex = i;//假设每轮外循环时,第一个为最小的数
for (int j = i+1; j < arr.length; j++) {
if(arr[j] < arr[minIndex]){//找到最小数
minIndex = j;//最小索引
}
}
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;//最小值换到最前
}
return arr;
}
2.2 堆排序
堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
3. 交换排序
3.1 冒泡排序
基本思想
依次将相邻的两数据进行比较,如果比后面的数据大就往上冒。一趟一趟的比较,比如,第一趟:先比较第1个与第2个数,大的往后放,再比较第2个与第3个数,大的往后放…第一趟比完之后,最后一位必定是最大的;第二趟,再次两两比较,但是比较到第n-2与n-1就停止,因为最后一位已经确定是最大;第三趟,最后的两位确定,比较到n-3与n-2停止。
PS:冒泡排序最简单最容易理解,但是比较慢,实际场景中不推荐使用
排序过程
以数列{3,1,5,4,2}为例,【后为已经比较完的,[ ]中为正要两两比较的数
第一趟:
[1 3] 5 4 2 3大于1,交换
1 [3 5] 4 2 3小于5,不交换
1 3 [4 5] 2 5大于4,交换
1 3 4 [2 5] 5大于2,交换
第一趟结束,最后一位固定:5,即1 3 4 2 【5
第二趟:
[1 3] 4 2 【5 1小于3,不交换
1 [3 4] 2 【5 3小于4,不交换
1 3 [2 4] 【5 4大于2,交换
第二趟结束,后两位固定:4,5,即1 3 2 【4 5
……
后面几趟同理,直到排完
代码实现
public static void sort(int[] arr){
for (int i = 0; i < arr.length; i++) {
//因为比较的是arr[j]与arr[j+1],所以减1
//因为第i趟比较完后,arr[arr.length-i]后的为有序
for (int j = 0; j < arr.length-i-1; j++) {
//把大的数往上冒
if(arr[j] > arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
3.2 快速排序
基本思想
快排是冒泡排序的一种改进。
一般步骤:
1)选择一个基准值
2)根据基准值,通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小
3)然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
排序过程
快速排序有三种实现方式——
快速排序总代码
static void quickSort(int[] arr,int low,int high){
//如果low=high,则说明数组只有一个元素,无需再处理
//如果low>high,则说明上次中枢元素的位置就是low或high,这种情况下分区不存,也不用处理
if(low<high){
//对分区进行排序整理
int centerIndex = part1(arr, low, high);//调用对应的part1/2/3三种实现
//对中枢元素左右的数组快速排序递归
//以centerIndex为界,分为三部分[low, centerIndex-1],[centerIndex],[centerIndex+1, high]
quickSort(arr, low, centerIndex-1);
quickSort(arr, centerIndex+1, high);
}
}
1. 实现一
初始化时,border指针指向中枢元素(用以分隔前后元素),low指针指向中枢元素后的元素,high指针指向最后一位元素。
整个过程,high指针不动,只向尾移动low指针,low指针找到比中枢元素小的元素,并与border指针指向的下一元素交换,然后border指向这个小的元素,low继续后移,直到low与high重合为止。
过程示意图:以{5,2,6,4,3,8,1,7}为例
快速排序实现代码(一)
static int part1(int[] arr,int low,int high){
int border = low;
int center = arr[low];//以第一个元素为中枢元素
//通过i来移动,这里的i相当于上文的low,这里的low则用于确定数组边界
for (int i = low+1; i <= high; i++) {
if(arr[i] < center){
border++;
int temp = arr[i];
arr[i] = arr[border];
arr[border] = temp;
}
}
//如果border没有移动,则说明后面的元素都比中枢元素大
//如果border移到high位置,则说明所有元素小于中枢元素
int temp = arr[border];
arr[border] = arr[low];
arr[low] = temp;
return border;
}
2. 实现二
如果low指针不是指向中枢元素,则使用low指针找到比中枢元素大的元素,找到后与high指针指向元素交换;
如果high指针不是指向中枢元素,则使用high指针找到比中枢元素小的元素,找到后与low指针指向元素交换。
过程示意图:以{5,2,6,4,3,8,1,7}为例,low初始时指向中枢元素
快速排序实现代码(二)
static int part2(int[] arr,int low,int high){
int centerIndex = low;//以第一个元素为中枢元素
do {
if(centerIndex != high){//low指针指向中枢元素时,移动high指针
//如果high指针指向元素小于中枢元素,则交换位置
if(arr[high] < arr[centerIndex]){
int temp = arr[high];
arr[high] = arr[centerIndex];
arr[centerIndex] = temp;
centerIndex = high;//中枢元素换到high指针处
}else{//不小于则指针前移继续找
high--;
}
}else{//high指针指向中枢元素时,移动low指针
//与上个if语句同理
if(arr[low] > arr[centerIndex]){
int temp = arr[low];
arr[low] = arr[centerIndex];
arr[centerIndex] = temp;
centerIndex = low;
}else{
low++;
}
}
} while (low != high);//退出条件为low=high,即两指针重合
return centerIndex;
}
3. 实现三
中枢元素先不移动,但是low指针与high指针移动,low找大于中枢元素的元素,high找小于中枢元素的元素,然后交换low与high的元素,直到low=high为止,最后将中枢元素与前半部分数组的最后一个元素交换。
过程示意图:以{5,2,6,4,3,8,1,7}为例
快速排序实现代码(三)
static int part3(int[] arr,int low,int high){
int centerIndex = low;//以第一个元素为中枢元素
int center = arr[centerIndex];
low++;//low指向中枢元素后第一个元素
do {
//high与low是两个不同步的循环,所以使用了两个while
//如果high指针未超出low指针
while(low<high){
if(arr[low] >= center){
break;//找到大于等于中枢元素的元素
}else{
low++;
}
}
//如果high指针未超出low指针
while(low<high){
if(arr[high] <= center){
break;//找到小于等于中枢元素的元素
}else{
high--;
}
}
int temp = arr[low];
arr[low] = arr[high];
arr[high] = temp;
} while (low != high);//退出条件为low与high重合
//如果low与high指针的元素小于中枢元素,则与arr[low]交换,否则与arr[low-1]交换
if(center > arr[low]){
int temp = arr[low];
arr[low] = arr[centerIndex];
arr[centerIndex] = temp;
centerIndex = low;
}else{
int temp = arr[low-1];
arr[low-1] = arr[centerIndex];
arr[centerIndex] = temp;
centerIndex = low-1;
}
return centerIndex;
}
4. 归并排序
基本思想
分治法的思想。
分:归——递归拆分,将数组拆成更小的数组,直至数组只包含一个元素。
治:并——合并,将小数组两两合并,合并时排序。
排序过程
以{5,3,9,7,1,8,2,6,4}为例
先将数组依次从中间位置拆开为两个更小的数组,直至拆到只剩一个元素
依次将小数组组合,组合的同时排序
关于合并的具体实现: 以{1,3,5,7,9}与{2,4,6,8}为例
代码实现
排序总代码实现
/**
* 归并排序的函数入口
* @param arr
* @param start
* @param end
*/
public static void sort(int[] arr,int start,int end){
partition(arr,start,end);
}
分:将数组从中间位置一份为二
/**
* 递归划分数组
* @param arr
* @param start
* @param end
*/
private static void partition(int[] arr,int start,int end){
//递归调用的退出条件为start大于等于end
if(start < end){
int mid = (start+end)/2;//数组的中间位置
//数组分为[start,mid]与[mid+1,end]两部分
//递归调用,不断将数组对半分
partition(arr,start,mid);
partition(arr,mid+1,end);
//合并划分后的数组
merge(arr, start, end, mid);
}
}
合:将两个小数组合并,且排序
/**
* 合并数组,在合并过程中对数组排序
* @param arr
* @param start
* @param end
* @param mid
*/
private static void merge(int[] arr,int start,int end,int mid){
int i = start;//左序数组的指针
int j = mid+1;//右序数组的指针
int t = 0;//临时数组的指针
int[] temp = new int[arr.length];//临时数组,用于存放排序合并后的数列
while(i<=mid && j<=end){
//比较,将两个指针指向的数中较小的那个放入临时数组
if(arr[i] <= arr[j]){
temp[t] = arr[i];
t++;
i++;
}else if(arr[i] > arr[j]){
temp[t] = arr[j];
t++;
j++;
}
}
//如果两两比较完后,左边还有剩余元素,则把左边剩余填入临时数组
while(i <= mid){
temp[t] = arr[i];
t++;
i++;
}
//同理,填入右边剩余元素
while(j <= end){
temp[t] = arr[j];
t++;
j++;
}
//将临时数组的元素全部拷贝回原数组
t = 0;//从头拷贝,所以t先归0
while(start <= end){
arr[start] = temp[t];
start++;//当start加到不小于end时完成拷贝
t++;
}
}
5. 基数排序
基数排序(radixsort)则是属于“分配式排序”(distributionsort),基数排序法又称“桶子法”(bucketsort)或binsort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O(nlog®m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的比较性排序法。