基本概念
排序算法的稳定性
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
分类
- 内部排序:数据都在内存中(关注如何使得算法的时间空间复杂度更低)
- 外部排序:数据太多,无法全部放入内存中,需要将待排序的记录存储在外部存储器(磁盘)上,无法一次性装入内存,所以需要在内存与外部存储器之间进行多次数据交换,所以还需要关注如何使得读写磁盘次数更少。
内部排序
插入排序
直接插入排序
插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开 始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相 等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳 定的。
-
时间复杂度:主要来自于对比关键字、移动元素。若有n个元素,则需要n-1趟处理。
- 最好:原本就是排好序的,所以n-1趟处理就只需要对比关键字一次而不需要交换元素——O(n);
- 最坏:原本是逆序排放,每一次都需要比较,移动——O(n^2)
- 平均:O(n^2)
-
空间复杂度:O(1)。
-
使用场景:如:把新的数据插入到已排好的数据列中
-
java实现:
public static void main(String[] args){ int arr[] = {7,5,3,2,4}; //插入排序 for(int i = 1;i<arr.length;i++){ //外层循环,从第二个开始比较 for(int j = i;j>0;j--){ //与之前已经排好序的进行比较,如果后面的数据小于前面的则交换 if(arr[j]<arr[j-1]){ int temp = arr[j-1]; arr[j-1] = arr[j]; arr[j] = temp; }else { //插入完毕,退出循环 break; } System.out.println(arr[0]+"->"+arr[1]+"->"+arr[2]+"->"+arr[3]+"->"+arr[4]+"\n"); } } }
5->7->3->2->4 5->3->7->2->4 3->5->7->2->4 3->5->2->7->4 3->2->5->7->4 2->3->5->7->4 2->3->5->4->7 2->3->4->5->7
希尔排序
是插入排序的一种,它是针对直接插入排序算法的改进。过比较相距一定间隔的元素来进行,各趟比较所用的距离随着算法的进行而减小,直到只比较相邻元素的最后一趟排序为止。
希尔排序是先追求表中元素部分有序,再逐渐逼近全局有序。
先将待排序表分割成若干形如L[i,i+d,i+2d,…,i+kd]的特殊子表,然后对各个子表分别进行直接插入排序。初始第一趟:d = length/2,然后缩小增量d,重复上述过程,直到d=1为止。
- 空间复杂度:O(1)
- 时间复杂度:它和增量序列的选择有关,目前无法用数学手段证明确切的时间复杂度,最坏的是O(n2),当n在某个范围内,可达到O(n1.3),所以还是比直接插入的优化一些的。
- 稳定性:不稳定,可以自己举例子证明一下。
- 适用性:只是适用于顺序表,而不能适用于链表(因为这里必须要有随机访问的特性才可以)。
public static void main(String[] args){
int arr[] = {7,5,3,2,4};
//希尔排序
for(int i = arr.length / 2;i > 0;i = i/2){//控制步长
for(int j = i;j < arr.length; j++){
for (int k = j; k > 0 && k - i >= 0; k -= i) {
if (arr[k] < arr[k - i]) {
int temp = arr[k - i];
arr[k - i] = arr[k];
arr[k] = temp;
} else {
break;
}
}
}
}
}
交换排序
冒泡排序
重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。从前往后,大的向后,会把最大的元素渐渐“冒”出来;从后往前,小的向前,会把最小的元素渐渐“冒”出来。
- 空间复杂度:O(1)
- 时间复杂度:最好O(n);最坏O(n2);平均O(n2)
- 稳定性:稳定的
- 适用性:可以用于链表
//从左往右冒泡
public static void bubbleSort1(int[] arr){
for(int i=arr.length-1; i>0; i--){
for(int j = 0;j < i; j++){
if(arr[j] > arr[j+1]){
swap(arr,j,j+1);
}
}
}
}
//改进
public static void bubbleSort2(int[] arr){
int flag;//标记
for(int i=arr.length-1; i>0; i--){
flag = 0;//初始化标记为0
for(int j = 0;j < i; j++){
if(arr[j] > arr[j+1]){
swap(arr,j,j+1);
flag = 1;//发生交换,变为1
}
}
// 若没发生交换,则说明数列已有序
if (flag == 0){
break;
}
}
}
//交换
public static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
运行结果:
请输入要排序的数组长度:
20
请输入要排序的数组:
9 99 10 48 29 89 45 3 7 88 29 34 19 48 29 65 96 20 58 11
1排序后的数组为:
3 7 9 10 11 19 20 29 29 29 34 45 48 48 58 65 88 89 96 99
2排序后的数组为:
3 7 9 10 11 19 20 29 29 29 34 45 48 48 58 65 88 89 96 99
冒泡排序应该是最容易理解和编写代码的排序了吧!!!
快速排序
参考笔记:https://www.cnblogs.com/skywang12345/p/3596746.html
重点来了朋友们!!!!
快排是所有排序算法里面考察频率最高的一个!!!
好好学啊友友们!!!理解不了咱就记死它!!!
算法思想:
在待排序表中任意选取一个元素pivot作为枢轴(基准),通常取得是首元素,通过一趟排序将待排序表划分为独立的两部分L[1,…,k-1]和L[k+1,…,n],使得L[1,…,k-1]中的所有元素小于pivot,L[k+1,…,n]中的所有元素大于等于pivot,则pivot就放在了其最终位置L(k)上,这个过程称作一次划分。然后分别递归地对两个子表重复上述过程,直到每部分只有一个元素或空为止,即达到了所有元素均放在了其最终位置上。
- 稳定性:不稳定
- 时间复杂度:平均O(NlogN),最坏O(N^2)
算法流程(超详细版):
- 从数列中挑出一个基准值,为了方便选择第一个;
- 将所有比基准值小的摆放在基准前面,所有比基准值大的摆在基准的后面(相同的数可以到任一边);在这个分区退出之后,该基准就处于数列的中间位置k。
- 如何将小于、大于基准数的值分别放在两边呢?采用双指针法,从数组的两端分别进行对比。
- 先从最右边的位置向左找直到找到一个小于基准数的值,记录下该值的位置(记为i);
- 再从最左位置往右直到找到一个大于基准数的值,记录下该值的位置(记为j);
- 如果位置i<j,则交换这两个位置上的值,然后继续从j-1的位置往前,i+1的位置往后重复上面比对基准数然后交换的步骤。
- 如果执行到了i=j,表示本次比对已经结束,将最后i的位置的值与基准数做交换,此时基准数就找到了临界点的位置k,位置k两边的数组都比当前位置k上的基准值或更小或更大。
- 递归地把"基准值前面的子数列"和"基准值后面的子数列"进行排序。
public static void quickSort(int[] arr){
if(arr == null || arr.length == 0 || arr.length == 1){
return;
}
partition(arr,0,arr.length-1);
}
//核心代码部分
public static void partition(int[] arr , int low , int high){
if (low > high){
return;
}
//设定基准值pivot
int pivot = arr[low];
int i = low,j = high;
while (i != j){
//从右往左直到找到比pivot小的数
while (arr[j] >= pivot && i < j){
j--;
}
//从左往右直到找到比pivot大的数
while (arr[i] <= pivot && i < j){
i++;
}
//已经找到或者已经i>=j了,就交换位置
if (i < j){
swap(arr,i,j);
}
}
//基准数位置(中间)
arr[low] = arr[i];
arr[i] = pivot;
//递归,继续向基准的左右两边执行和上面同样的操作
//i的索引处为上面已确定好的基准值的位置,无需再处理
partition(arr,low,i-1);
partition(arr,i+1,high);
}
//交换
public static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
//运行结果
请输入要排序的数组长度:
20
请输入要排序的数组:
9 99 10 48 29 89 45 3 7 88 29 34 19 48 29 65 96 20 58 11
排序后的数组为:
3 7 9 10 11 19 20 29 29 29 34 45 48 48 58 65 88 89 96 99
今日推歌
----《手掌心》 丁当
一干而尽
爱恨嗔痴的幻影
我敬你一杯一干二净的黎明
我在南极
憧憬你的北极星
我等你
不信心心不相印