排序方式笔记(java
)
1、冒泡排序
一种简单的排序方法,就是先将一个数据放在符合条件的位置,在将其它数据继续放在符合条件的位置。
实现原理:
- 比较相邻的两个数据,如果前面的数据大于后面的数据,就交换两个数据的位置。
- 对每一对相邻元素进行操作,就会发现,最大的数据就会放在元素的最后面。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vEtdoCfl-1637486703676)(https://i.loli.net/2021/11/14/xyi6sIZXbSfE73z.png)]
代码实现:
// array排序的数组,length数组的长度
public static void sort(int[] array,int length){
int temp;
for (int i = length-1;i>0;i--){
for(int j = 0;j<i;j++){
if(array[j]>array[j+1]){
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
}
}
时间复杂度分析:
冒泡排序使用双重循环,其中内层循环才是真正的执行代码。
- 比较次数:是每次冒泡中
未排序个数-1
,总的排序次数就为:(N-1)+(N-2)+.....+2+1 = N^2/2 - N/2
- 交换次数:假设最坏的情况(逆序),每次比较都会交换元素,总的交换次数
(N-1)+(N-2)+.....+2+1 = N^2/2 - N/2
- 总的执行次数:比较次数与交换次数之和,
O(N^2)
冒泡排序对于一些数据量较少的排序具有优势,但是随着数据量增加,执行次数成倍增加,所以对于数据量较大的排序使用冒泡排序就得不偿失了。
2、选择排序
选择排序顾名思义,就是选着满足条件的数据,放到指定的位置上。
实现原理:
- 每一次选择假设第一个元素是最小的元素,将这个元素的索引保存下来。
- 这个元素和后面的所有元素进行比较,如果发现小于这个元素的值,就将索引改变对应的值。
- 比较完后,将这个元素和最小索引对应的元素进行交换。
代码实现:
// array需要排序的数组,length数组元素的个数
public static void sort(int[] array,int length){
int temp;
for(int i=0;i<length-1;i++){
int minIndex = i;
for(int j=i+1;j<length;j++){
if(array[minIndex]>array[j]){
minIndex = j;
}
}
// 判断是否需要交换
if(minIndex!=i){
temp = array[i];
array[i] = array[minIndex];
array[minIndex] = temp;
}
}
}
时间复杂度分析:
选择排序也使用了双层循环,内层实现数据的比较,外层实现数据的交换。
- 比较次数:每次选择都是
未排序的数据个数-1
,总比较次数:(N-1)+(N-2)+...+2+1 = N^2/2-N/2
- 交换次数:
N-1
- 执行次数:
N^2/2+N/2-1
所以它的时间复杂度就为:O(N^2)
,也只适合小规模数据的排序。
3、插入排序
一种稳定且简单的排序方式。
实现原理:
- 将数据分为两组,一组已排序,一组未排序的。
- 找到未排序的第一个数据,向已排序中进行逆序插入。
- 比较插入元素和已排序的每一个元素,如果插入元素比已排序的元素小,就交换两个元素的位置,直到找到一个以排序中的元素比插入元素小就停止。
代码实现:
// array待排序的数组,length数组的元素个数
public static void sort(int[] array,int length){
int temp;
for(int i=1;i<length;i++){
for(int j=i;j>0;j--){
// 如果插入元素比已排序中的元素大,就不再交换
if(array[j-1]<array[j]){
break;
}
// 如果插入元素比已排序中的元素小,就交换两个元素的位置
temp = array[j-1];
array[j-1] = array[j];
array[j] = temp;
}
}
}
时间复杂度分析:
插入排序使用了双层循环,在内层循环中执行元素的比较和元素的交换。
- 比较的次数:假设每个未排序的元素都会和已排序的元素进行比较,总的比较次数:
1+2+...+(N-2)+(N-1) = N^2/2-N/2
- 交换的次数:假设每个插入的数都会和已排序的数进行交换,总的交换次数:
1+2+...+(N-2)+(N-1) = N^2/2-N/2
- 总的执行次数:
N^2-N
所以插入排序的时间复杂度为:O(N^2)
,也应用对于小规模数据的排序。
4、希尔排序
希尔排序是插入排序的一种,但是是优化插入排序的改进版。
实现原理:
- 选定一个增量,按照增量给数组进行分组。
- 在分好组的数组中进行插入排序。
- 减少增长量,最小减小为1,重复第二个步骤。
代码实现:
// array带排序的数组,length数组的长度
public static void sort(int[] array,int length){
int h = 1;
// 确定增长量
while (h<length/2){
h = h*2+1;
}
int temp;
// 排序,当增长量为0时就结束循环
while(h>=1){
// 找到待插入的元素
for(int i=h;i<length;i+=h){
// 将待插入的元素插入到已排序的序列中
for(int j=i;j>0;j-=h){
if(array[j-h]<array[j]){
break;
}
// 满足交换的条件
temp = array[j-h];
array[j-h] = array[j];
array[j] = temp;
}
}
h/=2;
}
}
时间复杂度分析:
希尔排序的时间复杂度根据序列来决定的,它时时间复杂度介于O(nlog(n)) 与 O(n^2)
之间,希尔排序是一个不稳定的排序,会随着序列的改变而改变事件复杂度。
5、归并排序
归并排序采用的是分治算法,先将排序的数组进行分组,然后将分组的数据进行排序。
排序原理:
- 将一个数组尽量分成两组等分的数组,并且使用递归的方式将每一个子组继续进行拆分,直到拆分为一个元素为止。
- 将相邻的子组进行合并成一个大组。
- 不断重复2,直到合并成一个大组。
合并原理:
- 初始话三个指针,分别指向left,mid+1,和辅助数组的left。
- 比较
p1
和p2
的大小,如果将小的数据放到辅助数组的i对应的位置,并且下标加1。
- 重复上面的步骤,直到将所有的元素都合并完成。
- 将辅助数组的元素全部重新复制到原数组中。
代码实现:
public static void sort(int[] array,int left,int right){
// 满足退出条件,当这部分这有一个元素的时候
if(right<=left){
return;
}
// 分
int mid = (left+right)/2;
// 进行递归
sort(array,left,mid);
sort(array,mid+1,right);
// 递归完成后进行合并
int i = left;
int p1 = left;
int p2 = mid+1;
// 定义一个辅助数组
int[] temp = new int[array.length];
// 定义一个临时交换中介
int x;
// 当左边和右边都没有合并完
while (p1<=mid && p2 <= right){
if(array[p1]>array[p2]){
temp[i++] = array[p2++];
}else{
temp[i++] = array[p1++];
}
}
// 如果p1部分没有合并完成
while (p1<=mid){
temp[i++] = array[p1++];
}
// 如果p2部分没有合并完成
while (p2<=right){
temp[i++] = array[p2++];
}
// 将排序好的数组复制到原数组中
for(int index = left;index<=right;index++){
array[index] = temp[index];
}
}
时间复杂度分析:
假设元素一共有N个,使用归并排序拆分的次数为log2(N)
,所以一共存在log2(N)
层,每一层需要的比较次数为2^K
,K为层数的序号。所以一共需要比较的次数为:log2(N) * 2 ^ log2(N) = N * log2(N)
,使用大O记法,就为N * log(N)
。由于归并排序的过程中采用了一个辅助数组来临时保存数组中的数据,所以在空间复杂度上有很大的缺点,这是一个典型的用空间来换取时间的算法。
6、快速排序
快速排序的本质就是分治算法,分而治之,将一个数组中的数据分成两个部分,然后根据递归的思想将每个部分进行处理。
实现原理:
选取一个主元,将数据中小于主元的数据移动到主元的左边,将大于主元的数据移动到数据的右边,这样就分成了两个部分,在每一个部分中也可以继续取一个主元,然后重复操作。
切分原理:
- 找到一个主元,创建两个指针分别指向数组的头部和尾部。
- 先从尾部查询,如果查询到一个小于主元的数据就停止,记录下尾部的指针。
- 再从头查询,如果查询到一个大于主元的数据就停止,记录下头部的指针。
- 判断头指针和尾指针是否重合,如果没有重合就交换这两个指针对应的元素,如果重合就分好组。
- 如果分好组,就将主元和尾指针指向的元素进行交换。
代码实现:
// 快排的递归排序,array排序的数组,left左边下标,right右边下标
public static void quickSort(int[] array,int left,int right){
if(left>=right){
return;
}
int key = array[left];
int p1 = left; // 左边开始检查的下标
int p2 = right+1; // 右边开始检查的下标
int temp;
// 分块
while (true){
// 先从右边开始检查
while (array[--p2]>key){
// 如果一直检查到最左边
if(p2 == left){
break;
}
}
// 再从左边开始检查
while (array[++p1]<key){
if (p1 == right){
break;
}
}
// 判断两端是否重合
if (p1>=p2){
break;
}
// 交换查询到的数据
temp = array[p1];
array[p1] = array[p2];
array[p2] = temp;
}
// 将主元移动到对应的位置
temp = array[left];
array[left] = array[p2];
array[p2] = temp;
// 将子快进行递归排序
quickSort(array,left,p2-1);
quickSort(array,p2+1,right);
}
时间复杂度分析:
- 最好的情况:
O(N)
- 最坏的情况:
O(N^2)
- 平均情况:
O( N*log(N) )
说明快速排序是一个不稳定的排序方法。