一、十大算法详解
1、直接插入排序
1.1、基本思想
在要排序的一组数中,假设前面(n-1)[n>=2] 个数已经是排好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。
1.2、实例:![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/4ac89853a4aba1b7f683be4b8c6fb5d3.png)
1.3、用java实现
class insertSort{
public insertSort(){
int a[]={49,38,65,97,76,12,27,49};
int temp=0;
for(int i=1;i<a.length;i++){
int j=i-1;
temp=a[i];
for(;j>=0&&temp<a[j];j--){
a[j+1]=a[j];
}
a[j+1]=temp;
}
for(int i=0;i<a.length;i++){
System.out.println(a[i]);
}
}
}
2、冒泡排序
2.1、基本思想:
在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。
3.2、实例:![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/1d65e91d975775b29b363bb2562a0f3b.png)
3.3、用java实现
class bubbleSort{
public bubbleSort(){
int a[]={49,38,65,97,76,12,27,49};
int temp=0;
for(int i=0;i<a.length-1;i++){
for(int j=0;j<a.length-1-i;j++){
if(a[j]>a[j+1]){
temp=a[j+1];
a[j+1]=a[j];
a[j]=temp;
}
}
}
for(int i=0;i<a.length;i++){
System.out.println(a[i]);
}
}
}
3、快速排序
3.1、基本思想:
选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。
3.2、实例:
3.3、用java实现
class quickSort{
public static void main(String[] args){
int a[]={49,38,65,97,76,12,27,49};
if(a.length>0){
quickSort(a,0,a.length-1);
}
for(int i=0;i<a.length;i++){
System.out.println(a[i]);
}
}
public static void quickSort(int[] list,int low,int high){
if(low<high){
int middle=getMiddle(list,low,high);
quickSort(list,low,middle-1);
quickSort(list,middle+1,high);
}
}
public static int getMiddle(int[] list,int low,int high){
int temp=list[low];
while(low<high){
while(low<high&&list[high]>=temp){
high--;
}
list[low]=list[high];
while(low<high&&list[low]<=temp){
low++;
}
list[high]=list[low];
}
list[low]=temp;
return low;
}
}
4、简单选择排序
4.1、基本思想:
在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。
4.2、实例:![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/a12917f14b2f211aff7034f65f12a7a7.png)
4.3、用java实现
class sort{
public static void swap(int arr[],int i,int j){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
public static void heapify(int tree[],int n,int i) {
int c1=2*i+1;
int c2=2*i+2;
int max=i;
if(c1<n&&tree[c1]>tree[max])
max=c1;
if(c2<n&&tree[c2]>tree[max])
max=c2;
if(max!=i){
swap(tree,max,i);
heapify(tree,n,max);
}
}
public static void build_heap(int tree[],int n){
int last_node=n-1;
int parent=(last_node-1)/2;
for(int i=parent;i>=0;i--){
heapify(tree,n,i);
}
}
public static void heap_sort(int tree[],int n){
build_heap(tree,n);
for(int i=n-1;i>=0;i--){
swap(tree,i,0);
heapify(tree,i,0);
}
}
public static void main(String[] args){
int a[]={49,38,65,97,76,12,27,49,78,34};
int n=a.length;
heap_sort(a,n);
for(int i=0;i<n;i++){
System.out.println(a[i]);
}
}
}
5、堆排序
5.1、堆定义:
堆是具有以下性质的完全二叉树:每个父结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个父结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f6ubkdV5-1587557129868)(E:\flyeat\我的小心得\数据结构\图片\大小顶堆.png)]
5.2、基本思想:
- 将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
- 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
- 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
5.3、代码实现
public class selectSort{
public static void swap(int arr[],int i,int j){
int temp=arr[i];
arr[i]=arr[j];
a[j]=temp;
}
public static void heapify(int tree[],int n int i) {
int c1=2*i+1;
int c2=2*i+2;
int max=i;
if(c1<n&&tree[c1]>tree[max])
max=c1;
if(c2<n&&tree[c2]>tree[max])
max=c2;
if(max!=i){
swap(tree,max,i);
heapify(tree,n,max)
}
}
public static void build_heap(int tree[],int n){
int last_node=n-1;
int parent=(last_node-1)/2;
for(int i=parent;i>=0;i--){
heapify(tree,n,i)
}
}
public static void heap_sort(int tree[],int n){
build_heap(tree,n);
for(int i=n-1;i>=0;i--){
swap(tree,i,0);
heapify(tree,i,0);
}
}
public static void main{
int a[]={49,38,65,97,76,12,27,49,78,34};
int n=a.length;
heap_sort(a,n);
for(int i=0;i<n;i++){
System.out.println(a[i]);
}
}
}
6、二分法插入排序(折半插入排序)
6.1、 核心思想
二分法插入排序是在插入第i个元素时,对前面的0~i-1元素进行折半,先跟他们中间的那个元素比,如果小,则对前半再进行折半,否则对后半进行折半,直到left>right,然后再把第i个元素前1位与目标位置之间的所有元素后移,再把第i个元素放在目标位置上。
6.2、代码实现
class Sort{
public static int[] binaryInsertSort(int[] a){
for(int i=0;i<a.length;i++){
int temp = a[i];
int low=0;
int high=i-1;
int mid=0;
while(low<=high){
mid=(low+high)/2;
if(temp<a[mid]){
high=mid-1;
}else{
low=mid+1;
}
}
for(int j=i-1;j>=low;j--){
a[j+1]=a[j];
}
if(low!=i){
a[low]=temp;
}
}
return a;
}
public static void main(String[] args){
int a[]={49,38,65,97,76,12,27,49};
a=binaryInsertSort(a);
for(int i=0;i<a.length;i++){
System.out.print(a[i]+" ");
}
}
}
7、希尔排序(缩小增量排序)
7.1 核心思想
核心思想:算法先将要排序的一组数按某个增量d(n/2,n为要排序数的个数)分成若干组,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。当增量减到1时,进行直接插入排序后,排序完成。
7.2 演示图![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/1cc2ae03ae73e32b579367d54dd409d1.png)
7.3、代码实现
class Sort{
public static int[] shellSort(int[] a){
int d = a.length / 2;
int temp=0;
while (true) {
d/=2;
for (int i=0;i<d;i++) {
for (int j=i;j+d<a.length;j+=d) {
for(int n=i;n+d<a.length;n+=d){
if (a[n]>a[n+d]) {
temp = a[n];
a[n] = a[n + d];
a[n + d] = temp;
}
}
}
}
if (d == 1)
break;
}
return a;
}
public static void main(String[] args){
int a[]={49,38,65,97,76,12,27,49,1};
a=shellSort(a);
for(int i=0;i<a.length;i++){
System.out.print(a[i]+" ");
}
}
}
8 、归并排序
8.1 核心思想
基本思想是:归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案”修补”在一起,即分而治之)。
细化来说,归并排序现将长度为n的无序序列看成是n个长度为1的有序子序列,首先做两两合并,得到n/2个长度为2的有序子序列,再做两两合并…不断重复这个过程,最终可以得到一个长度为n的有序序列。
8.2 演示图
8.2代码实现
class Sort{
public static void mergeSort(int[] a,int left,int right){
if(left<right){
int middle=(left+right)/2;
mergeSort(a,left,middle);
mergeSort(a,middle+1,right);
merge(a,left,middle,right);
}
}
public static void merge(int[] a,int left,int middle,int right){
int tempArry[]=new int[a.length];
int rightStart=middle+1;
int leftPos=left;
int temp=left;
while(left<=middle&&rightStart<=right){
if(a[left]<=a[rightStart]){
tempArry[temp++]=a[left++];
}else {
tempArry[temp++]=a[rightStart++];
}
}
while(left<=middle){
tempArry[temp++]=a[left++];
}
while(rightStart<=right){
tempArry[temp++]=a[rightStart++];
}
while(leftPos<=right){
a[leftPos]=tempArry[leftPos++];
}
}
public static void main(String[] args){
int a[]={49,38,65,97,76,12,27,49};
mergeSort(a,0,a.length-1);
for(int i:a){
System.out.print(i+" ");
}
}
}
9 基数排序
9.1 核心思想
基本思想是:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
基数排序的实现方法分为两种:MSD和LSD。
MSD:最高位优先法(Most Significant Digit First),先比较最高位,最高位分到一个桶中的,再按照第二位进行分桶…,知道分到最后一位,然后再从最小的桶中逐层向上,把元素都拿出来,即完成排序。
LSD:最低位优先法(Least Significant Digit First),先比较最低位,也就是个位,进行分桶,分桶过程中分到一个桶中的数据直接追加到桶中即可,无需排序。然后将所有同种的元素按桶的顺序拿出,重新组成序列,然后比较十位,进行分桶…直到比较到最高位,重新组成序列即可完成排序。
9.2 演示图(LSD为例)![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/84b16d8416b270d64f35def4cfc1df33.png)
9.3代码实现(LSD为例)
import java.util.List;
import java.util.ArrayList;
@SuppressWarnings("unchecked")
class Sort{
public static void basicSort(int[] array){
int max=0;
int digit=10;//0-9
int times=0;
//获得最大值
for(int num:array){
if(max<num)
max=num;
}
//获取最大值位数
while(max>0){
max/=10;
times++;
}
//建立10个集合(0-9)
List<ArrayList> baseList=new ArrayList<ArrayList>();
for(int i=0;i<digit;i++){
ArrayList list1=new ArrayList<>();
baseList.add(list1);
}
//进行times次分配和收集;
for(int i=0;i<times;i++){
for(int j=0;j<array.length;j++){
// 获取对应的位的值(pow是平方,i为0是个位,i为1是10位,i为2是百位)
int x=array[j]%(int)Math.pow(10,i+1)/(int)Math.pow(10,i);
ArrayList list2=baseList.get(x);
list2.add(array[j]);
}
int count=0;
//收集队列元素;
for(int j=0;j<digit;j++){
while(baseList.get(j).size()>0){
ArrayList<Integer> list3=baseList.get(j);
array[count]=list3.get(0);
list3.remove(0);
count++;
}
}
}
}
public static void main(String[] args){
int a[]={49,38,65,97,76,12,27,49};
basicSort(a);
for(int i:a){
System.out.print(i+" ");
}
}
}
10、桶排序
略
二、总结
1 稳定性
(1)稳定:冒泡排序、插入排序、二分插入排序、归并排序和基数排序。
(2)不稳定:选择排序、快速排序、希尔排序、堆排序。
2 平均时间复杂度
(1)O(n^2):直接插入排序,简单选择排序,冒泡排序、二分插入排序。
(2)O(nlogn):快速排序,归并排序,希尔排序,堆排序。快排是最好的, 其次是归并和希尔,堆排序在数据量很大时效果明显。
(3)在数据规模较小时(9W内):直接插入排序,简单选择排序差不多。当数据较大时:冒泡排序算法的时间代价最高。性能为O(n^2)的算法基本上是相邻元素进行比较,基本上都是稳定的。
3 排序算法的选择
3.1 数据规模较小(9W内)
(1)直接插入排序、冒泡排序:待排序列基本有序的情况下,对稳定性有要求;
(2)直接选择排序:待排序列无序,对稳定性不作要求;
3.2 数据规模很大
(1)归并排序:序列本身基本有序,对稳定性有要求,空间允许下。
(2)快速排序:序列本身无序。完全可以用内存空间,对稳定性没有要求,此时要付出log(n)的额外空间。 (3)堆排序:对稳定性没有要求,所需的辅助空间少于快速排序,
3.3 基数排序 (稳定) (1)在某个数字可能很大的时候,基数排序没有任何性能上的优势,还会浪费非常多的内存。 (2)一组数,这组数的最大值不是很大,更加准确的说,是要排序的对象的数目 和排序对象的最大值之间相差不多。比如,这组数 1 4 5 2 2,要排序对象的数目是 5 ,排序对象的最大值也是 5. 这样的情况很适合。
3.4 希尔排序 (不稳定) (1)对于中等大小的数组它的运行时间是可以接受的。 它的代码量很小,且不需要使用额外的内存空间。虽然有更加高效的算法,但除了对于很大的 N,它们可能只会比希尔排序快两倍(可能还达不到),而且更复杂。如果你需要 解决一个排序问题而又没有系统排序函数可用,可以先用希尔排序,然后再考虑是否值得将它替换为更加复杂的排序算法。 (2)希尔排序是对直接插入排序的一种优化,可以用于大型的数组,希尔排序比插入排序和选择排序要快的多,并且数组越大,优势越大。