排序算法
https://visualgo.net/演示部分基础算法
1.排序算法概括
冒泡排序
选择排序
插入排序
希尔排序
归并排序
快速排序
堆排序
计数排序
桶排序
基数排序
2.排序算法详细
2.1 冒泡排序
冒泡排序是一种简单的排序算法,它重复的走访过要排序的数列,一次比较两个元素,如果他们的书序错误就把他们交换过来。
/**
* 冒泡排序
* 有缺陷,这种情况下在最优的情况下是O(n^2)
* @param array
* @return
*/
public int[] buddleSort(int[] array){
if(array.length == 0){
return array;
}
for(int i = 0; i < array.length; i++){
for(int j = 0; j < array.length-1-i;j++){
if(array[j] > array[j+1]){
int temp = array[j+1];
array[j+1] = array[j];
array[j] = temp;
}
}
}
return array;
}
最佳情况 O(n)
最差情况 O(n^2)
平均情况 O(n^2)
空间复杂度O(1)
2.2 选择排序
选择排序是表现最稳定的排序算法之一,无论什么数据进去都是O(n^2)的时间复杂度,用的时候,数据规模越小越好。不占用额外的内存空间。
原理:每趟找出最小的或者最大的,放到相应位置,重复n-1次;
/**
* 选择排序 ASC
*
* @param array
* @return
*/
public static int[] selectionSort(int[] array) {
if (array.length == 0)
return array;
for (int i = 0; i < array.length; i++) {
int minIndex = i;
for (int j = i; j < array.length; j++) {
if (array[minIndex] > array[j])
minIndex = j;
}
int temp = array[minIndex];
array[minIndex] = array[i];
array[i] = temp;
}
return array;
}
最佳情况 O(n^2)
最差情况 O(n^2)
平均情况 O(n^2)
空间复杂度O(1)
2.3 插入排序
插入排序排序的思想是确定一部分已经确定顺序的,然后拿一个数值来进行比较插入,使其部分有序,然后扩展到整体有序。
/**
* 插入排序
* @param array
* @return
*/
public static int[] insertionSort(int[] array) {
if (array.length == 0)
return array;
for (int i = 0; i < array.length-1; i++) {
if (array[i + 1] < array[i]) {
int j = i+1;
while(j != 0 && array[j - 1] > array[j] ){
int temp = array[j - 1];
array[j - 1] = array[j];
array[j] = temp;
--j;
}
}
}
return array;
}
最佳情况 O(n)
最差情况 O(n^2)
平均情况 O(n^2)
空间复杂度O(1)
2.4 希尔排序
希尔排序是建立在插入排序上的,插入排序每次只能将数据移动一位,比较低效,为了提升效率,采用分组进行排序。
用语言来解释一下希尔排序的想法:
确定增量,分组,进行插入排序
如何分组呢?五个数(1 2 3 4 5)来讲的话 gap=2
3-5
x=3 3compare3-2=1 insert
x=4 4compare4-2=2 insert
x=5 5compare5-2=3 insert
gap=2/2=1
x=2 2compare2-1=1 insert(此时1-2有序)
x=3 3compare(1-2) insert(此时1-3有序)
x=4 3compare(1-3) insert(此时1-4有序)
x=5 3compare(1-4) insert(此时1-5有序)
https://www.cnblogs.com/onepixel/articles/7674659.html(从这个博客拿的图片 侵删)
/**
* 希尔排序
* 默认采用第一次增量为length/2
* ASC
* @param array
* @return
*/
public static int[] ShellSort(int[] array){
if (array.length == 0)
return array;
int gap = array.length/2;
while(true){
gap /= 2;
for(int i = gap; i<array.length; i++){
int j = i;
int current = array[i];
while(j-gap>=0 && current<array[j-gap]){
array[j] = array[j-gap];
j = j-gap;
}
array[j] = current;
}
if(gap==1){
return array;
}
}
}
时间复杂度取决于gap的值
空间复杂度O(1)
2.5 归并排序
归并排序采用分治法,将已有序列的子序列进行合并得到完全有序的序列。若将两个有序表合并成一个有序表,称为2-路归并。
/**
* 归并排序
* 默认采用2-路归并
* @param array
* @return
*/
public static int[] MergeSort(int[] array){
if (array.length == 0)
return array;
int gap=2;
while(gap<=array.length){
for(int i=0; i+gap<array.length; i+=gap){
if(i+gap-1>=array.length-1){
Merger(array, i, i+gap-1, array.length-1);
}else{
Merger(array, i, i+gap/2-1, i+gap-1);
}
}
gap*=2;
}
return array;
}
/**
* 归并操作
* @param data
* @param low
* @param mid
* @param high
* @return
*/
public static int[] Merger(int data[], int low, int mid, int high){
int[] temp = new int[data.length];
int i=low;
int start=low;
int j=mid+1;
while(i<=mid && j<=high){
if(data[i]<data[j]){
temp[start++]=data[i++];
}else{
temp[start++]=data[j++];
}
}
while(i<=mid){
temp[start++]=data[i++];
}
while(j<=high){
temp[start++]=data[j++];
}
while(low<=high) {
data[low]=temp[low];
low++;
}
return data;
}
时间复杂度取决于取决于几路归并,二路归并的复杂度为O(log2^n)
空间复杂度O(n)
2.6 快速排序
快排算法是标定一个点位,比他小的放一边,大的放另外一边。
/**
* 快排
* @param array
* @return
*/
public static int[] QuickSort(int[] array) {
if(array.length==0){
return array;
}
Stack<Integer> stack=new Stack<Integer>();
stack.push(0);
stack.push(array.length);
while(!stack.empty()){
int right = stack.pop();
int left = stack.pop();
int index=Partition(array, left, right);
if((index-1)>left){
stack.push(left);
stack.push(index);
}
if((index+1)<right){
stack.push(index+1);
stack.push(right);
}
}
return array;
}
/**
* 快排第一次划分操作
* @param data
* @param low
* @param high
* @return
*/
public static int Partition(int data[], int low, int high){
int index=low;
low=index+1;
for(int x=index+1; x<high; x++){
if(data[x]<data[index]){
Swap(data, x, low);
low++;
}
}
Swap(data, index, low-1);
index=low-1;
return index;
}
最佳情况 O(n)
最差情况 O(n^2)
平均情况 O(log2^n)
空间复杂度O(1)
2.7 堆排序
堆排序是指利用堆这一数据结构进行的选择排序。
堆是具有以下性质的完全二叉树:每个结点的值都大于或者等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或者等于其左右孩子结点的值,称为小顶堆。
如图所示为一个大顶堆。
对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子。
该数组从逻辑上来讲是一个对结构,用简单的公式描述一下堆的定义就是
大顶堆:arr[i] >= arr[2i+1]&&arr[i]>=arr[2i+2]
小顶堆:arr[i] <= arr[2i+1]&&arr[i]<=arr[2i+2]
建立堆
堆排序本质上是一个个建立堆的过程,每成功建立一次之后,就会有最值出现,经过交换,将最值调整到末尾,然后在经过堆的建立得到第二大第三大的值,通过交换,使其序列有序。`
/**
* 构建堆
* @param data
*/
public static void buildHeap(int data[]) {
for(int i=data.length/2-1; i>0; i--){
adjust(data, i);
}
}
/**
* 调整操作 堆排序
* @param data
* @param location
* @return
*/
public static void adjust(int data[],int location) {
int temp=data[location];
for(int k=location*2+1; k<data.length; k=2*k+1){
if(k+1<data.length&& data[k]<data[k+1]){
k++;
}
if(data[k]>temp){
data[location]=data[k];
location=k;
}else{
break;
}
}
data[location]=temp;
}
/**
* 堆排序
* 大顶堆
* @param array
* @return
*/
public static void HeapSort(int[] array) {
buildHeap(array);
for(int j=array.length-1; j>0; j--){
Swap(array, 0, j);
adjust(array, j );
}
}
构建初始堆的复杂度为O(n)
需要构建n次 由于每次n的规模都在缩小
所以比nlogn小
近似看做O(nlogn)
2.8 计数排序
计数排序先找到最大值,然后用从最小值到最大值的一个空间,用下标来代替数据进行数据的存储。
/**
* 计数排序
* @param array
* @return
*/
public static int[] CountingSort(int[] array) {
int max=0;
for(int x=0;x<array.length;x++){
if(array[x]>max){
max=array[x];
}
}
int[] temp = new int[max+1];
for(int x=0;x<array.length; x++){
System.out.println(array[x]);
temp[array[x]]+=1;
}
return temp;
}
计数排序时间复杂度为O(k+n)
空间复杂度为O(k+n)
2.9 桶排序
桶排序是将待排序集合中处于同一个至于的元素存入同一个桶中,也就是根据元素值特性将集合拆分为多个区域,则拆分后形成的多个桶,从至于上看是处于有序状态的。对每个桶中的元素进行排序,则所有桶中元素构成的集合是已排序的。
桶排序的过程的主要在于两点,一是元素值域的划分,如何能够合理的分块,映射规则的设定应该如何设定?二是桶内排序算法的选择,不同的排序算法会导致桶排序的复杂度和稳定性不同。
/**
* 划分方式的不同和内部排序算法的不同会导致桶排序的不同
* 本次划分方式用间隔array.length来进行桶的划分
* 内部用的array.sort 底层为Timsort
* @param array
* @return
*/
public static int[] BucketSort(int[] array){
int max=0,min=0;
for(int i=0; i<array.length; i++){
if(array[i]>max) max=array[i];
if(array[i]<min) min=array[i];
}
//计算桶的数量
int bucketNum = (max-min)/array.length+1;
ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum);
for(int i=0; i<bucketNum; i++){
bucketArr.add(new ArrayList<Integer>());
}
//将每个元素放入桶中
for(int i=0; i<array.length; i++){
int num = (array[i]-min)/array.length;
bucketArr.get(num).add(array[i]);
}
//对每个桶进行排序
for(int i=0;i<bucketArr.size();i++){
Collections.sort(bucketArr.get(i));
}
//输出
int index=0;
for(int i=0; i<bucketArr.size();i++){
for(int j=0;j<bucketArr.get(i).size();j++){
array[index++]=bucketArr.get(i).get(j);
}
}
return array;
}
时间复杂度取决于装入N个桶,每个桶进行logM排序(采用timsort排序,如果采用其他排序时间复杂度不一样),时间复杂度为O(NLogM)
空间复杂度 O(N+M)
2.10 基数排序
基数排序采用了桶排序的思想,换分成0-9十个桶,然后根据个位十位百位以此类推来进行排序。
/**
* 基数排序
* @param array
* @return
*/
public static int[] RadixSort(int[] array){
//计算桶的数量
int bucketNum = 10;
//找最大值
int max=0,size=0;
for(int i=0;i<array.length;i++){
if(array[i]>max) max=array[i];
}
//找位数
while (max>0){
max/=10;size++;//求取这个最大值的位数,依次除以10;直到为0;
}
//x代表位数
int x=10;
for(int i=0;i<size;i++){
ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum);
for(int p=0; p<bucketNum; p++){
bucketArr.add(new ArrayList<Integer>());
}
if(i==0){
for(int j=0;j<array.length;j++){
int temp=array[j]%x;
bucketArr.get(temp).add(array[j]);
}
}else{
for(int j=0;j<array.length;j++){
int temp=array[j]/(x*i)%x;
bucketArr.get(temp).add(array[j]);
}
}
//输出
int index=0;
for(int z=0; z<bucketArr.size();z++){
for(int j=0;j<bucketArr.get(z).size();j++){
array[index++]=bucketArr.get(z).get(j);
}
}
}
return array;
}
时间复杂度为O(k*n)
空间复杂度为O(n+k)