算法概述
算法分类
十种常见排序算法可以分为两大类:
比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。
算法复杂度
相关概念
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
1.冒泡排序
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来,走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端。
算法描述:
- 从列表的第一个元素开始,依次比较相邻的两个元素。
- 如果第一个元素大于第二个元素,则交换它们的位置,使较大的元素排在后面。
- 继续比较第二个元素和第三个元素,依次类推,直到最后一个元素。
- 完成一轮比较后,最大的元素会被移到列表的最后面。
- 然后对剩下的元素重复以上步骤,直到没有需要比较的元素。
代码实现:
//冒泡排序
public static void sort(int[] arr){
for(int i=0;i<arr.length-1;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;
}
}
}
}
解释:外层循环 i 控制总共需要比较的趟数,最后一趟只剩一个元素,不需要再比较;
内层循环 j 控制每趟需要比较的次数,相邻元素依次比较,减1防止 j+1下标越界,减外层循环的趟数表示第几趟有几个元素已经排好,不需要再重复比较了。
2.选择排序
选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的未尾。以此类推,直到所有元素均排序完毕。(拿出最大或最小,然后在剩余中再拿最大或最小)
算法描述:
- 从待排序列表中选择最小(或最大)的元素,记为最小元素。
- 将最小元素与列表的第一个元素交换位置,使最小元素放在已排序区间的末尾。
- 然后将未排序区间的第一个元素与剩余元素中的最小元素交换位置,再次将最小元素放在已排序区间的末尾。
- 依次类推,每次从未排序区间中找到最小(或最大)的元素,放到已排序区间的末尾。
- 重复以上步骤,直到未排序区间为空。
代码实现:
//选择排序
public static void sort(int[] arr){
int length = arr.length; //获取数组长度
for(int i=0;i<length;i++){
int minIndex = i; //初始化一个变量记录最小值下标,从未排序的第一个开始
for(int j=i+1;j<length;j++){ //从第二个,遍历数组
if(arr[j]<arr[minIndex]){ //如果比记录的最小值更小
minIndex = j; //更新最小值下标
}
}
//一趟遍历后将最小值交换到第一个位置
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
解释:每趟遍历从未排序的元素中挑出来一个最小值放到前面
3.插入排序
插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O()的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。(将未排序的,在已排序中找到位置插进去)
算法描述:
- 从第一个元素开始,认为该元素已经是排好序的序列。
- 取出下一个元素,与已经排好序的序列从后往前比较,找到合适的位置插入。
- 重复步骤2,直到所有元素都插入完成。
代码实现:
//插入排序
public static void sort(int[] arr){
int temp,j; //记录要插入的元素
for(int i=1;i<arr.length;i++){ //总共要比较的趟数
temp = arr[i]; //取出未排序的第一个元素
for(j=i-1;j>=0&&temp<arr[j];j--){ //从已排序元素中从后往前找
arr[j+1] = arr[j]; //依次后移
}
arr[j+1] = temp; //将取出的元素插入
}
}
解释:从第二个元素(下标为1)开始,认为第一个已经排好,取出未排好的第一个元素,然后从已经排好的元素中从后往前找,如果比取出的元素大就后移,直到找到合适的位置就插入。
内层循环 当取出元素temp大于或等于比较元素arr[j]时退出,将取出元素插入到后面,即arr[j+1]
4.希尔排序
5.归并排序
6.快速排序
快速排序的基本思想是通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
算法描述:
- 选择一个基准元素(pivot)。
- 将数组分成两部分,小于基准元素的放在左边,大于基准元素的放在右边。
- 对左右两个子数组递归地进行快速排序。
- 合并左、基准元素、右三个部分,得到排好序的数组。
代码实现:
//快速排序
public static void sort(int[] arr,int begin,int end){
//如果区间不止一个元素
if(begin < end){
int temp = arr[begin];//将区间的第一个数作为基准值
int left = begin; //从左往右查找的指针,指向当前左位置
int right = end; //从右往左查找的指针,指向当前右位置
while (left < right){
//当右边的数大于基准值时略过,继续往左查找
while (left < right && arr[right] > temp){
right--;
}
arr[left] = arr[right];//将右边小于或等于基准值的数填到左边
//当左边的数小于等于基准值时略过,继续往右查找
while (left < right && arr[left] <= temp){
left++;
}
arr[right] = arr[left];//将左边大于基准值的数填到右边
}
//左右指针相遇,此时左边小于等于基准值,右边大于基准值
arr[left] = temp;//填入基准值
//对基准值左边的子区间递归排序
sort(arr,begin,left-1);
//对基准值右边的子区间递归排序
sort(arr,left+1,end);
}
//如果区间只有一个数,直接返回
else {
return;
}
}
解释:取第一个值作为基准值后,右边找小于等于基准值的数放到左区间(上述实现中将重复的基准值集合到左区间),左边找大于基准值的数放到右区间,当左右指针相遇时,此时的位置就是基准值的位置,将完整数组分成左右两部分(左边小于等于基准值,右边大于基准值),之后只需要对左右区间分别递归即可。