常用的排序算法有:
冒泡排序,选择排序,插入排序,归并排序,堆排序,快速排序等。
排序算法稳定性
稳定性的简单形式化定义为:如果Ai = Aj,排序前Ai在Aj之前,排序后Ai还在Aj之前,则称这种排序算法是稳定的。通俗地讲就是保证排序前后两个相等的数的相对顺序不变。
对于不稳定的排序算法,只要举出一个实例,即可说明它的不稳定性;而对于稳定的排序算法,必须对算法进行分析从而得到稳定的特性。需要注意的是,排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下可以变为稳定的算法,而稳定的算法在某种条件下也可以变为不稳定的算法。
1.冒泡排序
如果将记录交换的条件改成A[i] >= A[i + 1],则两个相等的记录就会交换位置,从而变成不稳定的排序算法。
时间复杂度:平均为 O(n2),最好情况 即已经排序的数组,O(n)
private static void sort(int[] arr) {
for (int i = 0;i<arr.length-1;i++)
for (int j=0;j<arr.length-1-i;j++){ //从前往后 从小到大
if (arr[j]>arr[j+1])
swap(arr,j,j+1);
}
}
private static void sort2(int[] arr) {
for (int i = 0;i<arr.length-1;i++)
for (int j=0;j<arr.length-1-i;j++){ //从前往后 从大到小
if (arr[j]<arr[j+1])
swap(arr,j,j+1);
}
}
冒泡排序的优化:
用一个flag来表示排序已经完成,没有发生交换,那么就跳出循环。
//冒泡排序优化
private static void sortBetter(int[] arr) {
boolean flag = false;
for (int i = 0;i<arr.length-1;i++)
{
flag = false;
for (int j=0;j<arr.length-1-i;j++)
{ //从前往后 从小到大
if (arr[j]>arr[j+1]) {
swap(arr,j,j+1);
flag = true;
}
}
if (flag = false)// 整个循环 都没有发生交换 说明 数组已经有序 跳出循环,无需进行排序
break;
}
}
2.简单选择排序
它的工作原理很容易理解:初始时在序列中找到最小(大)元素,放到序列的起始位置作为已排序序列;然后,再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
时间复杂度:平均:O(n2) 最好最差:O(n2)
选择排序是不稳定的排序算法,不稳定发生在最小元素与A[i]交换的时刻。
比如序列:{ 5, 8, 5, 2, 9 },一次选择的最小元素是2,然后把2和第一个5进行交换,从而改变了两个元素5的相对次序。
//简单选择排序,每次选择未排序序列中最小的放在 有序数列的末尾
//与冒泡排序的区别 冒泡排序每次遇到后面比前面小的数都要交换,而选择排序只交换 当前序列末尾的数和 未排序中最小的数
private static void sort(int[] arr) {
int min = 0;
for (int i= 0;i<arr.length;i++)
{
min = i;
for (int j=i+1;j<arr.length;j++){
if (arr[j]<arr[min])
min = j;// 只是将脚标给他
}
swap(arr,min,i); //交换 当前序列未排序最小的数和 已排序最后末尾的数
}
}
3.插入排序
- 从第一个元素开始,该元素可以认为已经被排序
- 取出下一个元素,在已经排序的元素序列中从后向前扫描
- 如果该元素(已排序)大于新元素,将该元素移到下一位置
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
- 将新元素插入到该位置后
- 重复步骤2~5
插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,比如量级小于千,那么插入排序还是一个不错的选择。 插入排序在工业级库中也有着广泛的应用,在STL的sort算法和stdlib的qsort算法中,都将插入排序作为快速排序的补充,用于少量元素的排序(通常为8个或以下)。
时间复杂度:O(n2) 最好情况:已排序 O(n) 最差:O(n)
稳定排序
//插入排序
// 首先认为第一个数时有序的,从后面的一个数开始往前插,如果前面一个数,大于这个tmp数,
//那么前面这个数后移一个,一直到前面的数小于tmp,那么tmp就在这个j的位置
private static void sort(int[] arr) {
//注意这个J
int j;
for (int i =1;i<arr.length;i++)
{
int tmp = arr[i];
for ( j =i;j>0 && arr[j-1]>tmp;j--){
arr[j]=arr[j-1];
}
arr[j]=tmp;
}
}
4.希尔排序
希尔排序,也叫递减增量排序,是插入排序的一种更高效的改进版本。希尔排序是不稳定的排序算法。
//希尔排序
//插入排序增强版
//常规插入排序只是一次比较间隔位大小,建个永远为一。
//希尔排序使用大间隔持续缩小,对间隔内元素分组,这样相邻较远的元素优先排序,最后变成插入排序
private static void sort(int[] arr) {
//间隔去 leng/2
for (int len = arr.length/2;len>0;len/=2)
{
for (int i = len;i<arr.length;i++){
int j = i;
while (j-len>=0 && arr[j]<arr[j-len]){
swap(arr,j,j-len);
j-=len;
}
}
}
}
在最优的情况下,时间复杂度为:O(n ^ (1.3) ) (元素已经排序好顺序)
在最差的情况下,时间复杂度为:O(n ^ 2);
5.快速排序
快排原理:
在要排的数(比如数组A)中选择一个中心值key(比如A[0]),通过一趟排序将数组A分成两部分,其中以key为中心,key右边都比key大,key左边的都key小,然后对这两部分分别重复这个过程,直到整个有序。
整个快排的过程就简化为了一趟排序的过程,然后递归调用就行了。
一趟排序的方法:
1,定义i=0,j=A.lenght-1,i为第一个数的下标,j为最后一个数下标
2,从数组的最后一个数Aj从右往左找,找到第一小于key的数,记为Aj;
3,从数组的第一个数Ai 从左往右找,找到第一个大于key的数,记为Ai;
4,交换Ai 和Aj
5,重复这个过程,直到 i=j
6,调整key的位置,把A[i] 和key交换
时间复杂度:O(nlogn)
private static void Quicksort(int[] arr, int low, int high) {
//递归 结束
if (low>high)
return;
int i = low;
int j =high;
int key = arr[low];//基准
while (i<j){
while (i<j && arr[j]>key)
j--;
while (i<j && arr[i]<key)
i++;
if (i<j)
swap(arr,i,j);
}//完成一次排序
swap(arr,i,low);//i 此时和就重合,交换key和i的位置 完成一次
//排左边
Quicksort(arr,low,i-1);
Quicksort(arr,i+1,high);
}
6.堆排序
利用最大堆或者最小堆完成排序。先初始化一个最大堆(从小到大排),然后取出堆顶元素,放在末尾,调整该堆使其符合堆的定义。不断重复上述,完成堆排序。
//堆排序
private static void sort(int[] arr) {
// 初始化堆 从 len/2处开始往上初始化
for (int len = arr.length/2-1;len>=0;len--){
adjust(arr,len,arr.length);
}
//调整堆 排序
for (int i = arr.length-1; i>=0;i--){
//交换首尾的元素
swap(arr,0,i);
//调整堆
adjust(arr,0,i);
}
}
private static void adjust(int[] arr,int left,int len) {
int tmp = arr[left];
for (int k = 2 * left + 1; k < len; k = 2 * k + 1) {
if (k + 1 < len && arr[k] < arr[k + 1])
k++;
if (tmp < arr[k]) {
arr[left] = arr[k];
left = k;
} else break;
}//都调整完
arr[left] = tmp;
}
private static void swap(int[] arr, int j, int i) {
int tmp = arr[j];
arr[j] = arr[i];
arr[i] = tmp;
}
7.归并排序
分治的思想,先将数组分为(二分法)若干份,然后直到分成一组只有2个,将这两个进行排序,创建临时数组,比较然后放入临时数组,然后递归完成此操作。
private static void sort(int[] arr) {
int [] tmp = new int[arr.length];
bingSort(arr,0,arr.length-1,tmp);
}
private static void bingSort(int[] arr, int left, int right,int [] tmp) {
if (left<right){
int mid = (left+right)/2;
bingSort(arr,left,mid,tmp);//左子序列 分割 left-mid 这一部分
bingSort(arr,mid+1,right,tmp);//右子序列
merge(arr,left,mid,right,tmp);//对左右子序列进行归并排序
}
}
private static void merge(int[] arr, int left, int mid, int right, int[] tmp) {
int i = left;
int j = mid+1;
int index = 0;
while (i<=mid &&j<=right )
{
if (arr[i]<arr[j])
tmp[index++] = arr[i++];
else
tmp[index++] = arr[j++];
}
while (i<=mid){
tmp[index++] = arr[i++];
}
while (j<=right)
tmp[index++] = arr[j++];
index = 0;
while (left<=right){
arr[left++] = tmp[index++];
}
}