排序 是对数据元素序列建立某种有序排列的过程。我们通常从三个方面来衡量排序算法的优劣,分别是 时间复杂度,空间复杂度 及 稳定性 1。
1. 直接插入排序
-
基本思想: 遍历数组中元素,插入到已排序数组的正确位置。
-
算法设计: 设置两个循环,一个循环遍历整个数组,另一个循环将 a[i] 与已排序部分比较,找到插入位置。
public void insertSort(int[] a){
for(int i=1; i<a.length; i++){ //循环遍历获取元素a[1],a[2],a[3]...
for(int j=i; j>0; j--){
//j=i,每次都先与已排序部分的最后一位比较大小。如果小往前继续比较。
if(a[j] < a[j-1]){
//swap(a[j],a[j-1])
int temp = a[j-1];
a[j-1] = a[j];
a[j] = temp;
}
}
}
}
- 算法性能:
时间复杂度:O(n2)
空间复杂度:O(1)
稳定
2. 希尔排序
- 基本思想: 希尔排序是在分组概念上的直接插入排序。小组内用直接插入排序,再把若干小组合并为一个小组。
希尔排序需有一个增量序列,所谓增量就是每次分组的个数,我们选择{n、n/2, (n/2)/2, … , 1} 作为我们的增量序列,这个是比较常用的。
- 算法设计: 最外层循环为增量序列。内层的两个循环与直接插入排序算法一致,只是在最内层循环比较时,每次不是 -1,而是 -step。
public static void shellSort(int[] a) {
//增量序列,每次 step/2
for(int step=a.length/2; step>0; step/=2) {
for(int i=step; i<a.length; i++) {
for(int j = i; j>=step; j-=step) {
if(a[j] < a[j-step]) {
//swap(a[j],a[j-step])
int temp = a[j];
a[j] = a[j-step];
a[j-step] = temp;
}
}
}
}
}
- 算法性能:
时间复杂度:O(n(lbn)2)
空间复杂度:O(1)
不稳定
3. 直接选择排序
- 基本思想: 先从待排数组中选择最小与数组中的第一个元素交换。然后将除第一个外剩余元素中最小的与数组中的第二个元素交换,以此类推。
- 算法设计: 设置两个循环。第一个循环遍历整个数组,第二个循环是从 i+1 开始,即总是在寻找未排序部分的最小值,然后与 i 交换。
public void selectSort(int[] a){
int n = a.length;
for(int i=0; i<n; i++){
int min = i;
//找到剩余元素的最小值的 下标 赋给 min
for(int j=i+1; j<n; j++){
if(a[j] < a[min]) min = j;
}
//下标为 min 的与未排序部分的第一位进行交换
if(min != i){
//swap(a[i],a[min])
int temp = a[i];
a[i] = a[min];
a[min] = temp;
}
}
}
- 算法性能:
时间复杂度:O(n2)
空间复杂度:O(1)
不稳定
4. 堆排序
- 基本思想: 将待排序序列构造成一个大顶堆 2,此时整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列。
一、 将无序序列构造成一个大顶堆
从最后一个非叶子结点开始(叶结点自然不用调整),从左至右,从下至上进行调整,构造大顶堆。
成功构造大顶堆后,整个序列的最大值就是堆顶的根节点。
二、将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端
三、重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
- 算法设计:
public static void sort(int []a){
int len = a.length;
for(int i=len/2-1;i>=0;i--){
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(a,i,len);
}
for(int j=len-1;j>0;j--){
//交换堆顶元素与末尾元素
int temp = a[0]; a[0] = a[j]; a[j] =temp;
//重新对堆进行调整,len 为 j
adjustHeap(a,0,j);
}
}
//调整根节点大于子节点
public static void adjustHeap(int []a,int i,int len){
int temp = a[i];//先存下当前节点的值
for(int k=i*2+1;k<len;k=k*2+1){//从i结点的左子结点开始,也就是2i+1处开始
if(k+1<len && a[k]<a[k+1]){//如果左子结点小于右子结点,k指向右子结点
k++;
}
if(a[k] >temp){//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
a[i] = a[k];
i = k;
}else{
break;
}
}
a[i] = temp;//将temp值放到最终的位置
}
学习参考: 图解排序算法之堆排序
- 算法性能:
时间复杂度:O(lbn)
空间复杂度:O(1)
不稳定
5. 冒泡排序
-
基本思想: 依次比较两个相邻的元素,将较大的放在后面。最大元素会被交换到数组最右边。
-
算法设计: 两个循环
外循环控制循环次数,每一次循环都会使剩余元素中的最大元素排到最后。
内循环依次比较各元素,执行交换,使最大元素沉至底部。
public static void bubbleSort(int[] a) {
int n = a.length;
for(int i=0; i<n-1; i++) {
for(int j=0; j<n-1-i; j++) {
//比较相邻的两个元素,比较 n-1-i 次
if(a[j]>a[j+1]) {
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
}
- 算法性能:
时间复杂度:O(n2)
空间复杂度:O(1)
稳定
6. 快速排序
- 基本思想: 对 nums[left … right] 进行排序
寻找一个点 (nums[p]) 作为基准元素,循环整个数组,比较交换元素使得 nums[left…p-1] 都小于等于 nums[p],nums[p+1…right]都大于nums[p]。
然后对nums[left…p-1],nums[p+1…right] 递归的用同样方法排序。
- 算法设计:
此算法将a[0] 作为基准元素
将数组 a[1… a.length-1] 中的元素循环与a[0]比较,使小于a[0]的在前,大于a[0]的在后。
经过这次循环后,p 所在的位置就是最后一个小于a[0]元素所在的位置,将基准元素与此元素交换(swap(a[p], a[0])),就可使nums[left…p-1] 都小于等于 nums[p],nums[p+1…right]都大于nums[p]。
public static void quickSort(int[] a,int left, int right) {
int sign,p,temp;
if(left < right) {
p = left;
sign = a[p];
for(int i=left+1; i<=right; i++) {
if(a[i] < sign) {
p++;
temp = a[p];
a[p] = a[i];
a[i] = temp;
}
}
temp = a[left];
a[left] = a[p];
a[p] = temp;
quickSort(a,left,p-1);
quickSort(a,p+1,right);
}
}
- 算法性能:
时间复杂度:O(n2)
空间复杂度:O(n)
不稳定
7. 归并排序
-
基本思想: 对 nums[left … right] 进行排序
先对 nums[left … mid] 排序,再对 nums[mid+1 … right] 排序,最后把这两个有序的子数组合并,整个数组就排好序了。
-
算法设计:
将数组分开分别排序,再合并。
注意合并算法。
public static int[] sort(int[] a, int left, int right) {
int mid = (left+right)/2;
if(left<right) {
//对 nums[left ... mid] 排序
sort(a,left,mid);
//对 nums[mid+1 ... right] 排序
sort(a,mid+1,right);
//合并
merge(a,left,mid,right);
}
return a;
}
//合并函数
public static void merge(int[] a, int left, int mid, int right) {
int[] temp = new int[right-left+1];
int i = left;
int j = mid + 1;
int k = 0;
while(i<=mid && j<= right)
if(a[i]<a[j]) {
temp[k++]=a[i++];
}else {
temp[k++]=a[j++];
}
while(i<=mid) temp[k++]=a[i++];
while(j<=right) temp[k++]=a[j++];
for(int x=0; x<temp.length; x++)
a[x+left] = temp[x];
}
- 算法性能:
时间复杂度:O(nlbn)
空间复杂度:O(n)
稳定