注:在本篇文章中进行对插入排序和选择排序的解释分析,排序均是从小到大
一、排序的概念
排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性:所谓稳定性,是指在排序时数值相同的元素在排序前后位置顺序不发生变化,这样,这个排序算法就是稳定的。
如图:
常见的排序算法:
二、排序算法的实现
1.插入排序
基本思想:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。
(1)直接插入排序
实现思路:
1.定义一个变量 i 从第二个元素开始用来遍历数组中的元素。
2.定义一个变量 j 在 i 下标的前面元素位置用来和 i 下标对应的元素进行比较
3.定义一个 tmp 变量用来记录 i 下标交换前的值。
代码如下:
public void insertSort(int[] array){
//实现对数组整体元素的遍历
for (int i = 1; i < array.length; i++) {
//记录下第 i 个元素的值
int tmp = array[i];
//实现一个变量用来记录上一个元素
int j = i-1;
for (; j >= 0; j--) {
//判断 i 的前一个数 是否大于后一个数
if(array[j] > tmp){
//如果大于,就将前一个数直接赋值给 array[i]
array[j + 1] = array[j];
}else{
break;
}
}
//因为每次循环后 j 的位置会在 i 的后两个位置处
//所以要将较小的 tmp 的值 给 array[j + 1];
array[j + 1] = tmp;
}
}
代码分析
- 元素集合越接近有序,直接插入排序算法的时间效率越高
- 时间复杂度:O(N^2)
- 空间复杂度:O(1),它是一种稳定的排序算法
- 稳定性:稳定
如上图,是进行过一次交换后 i 和 j 现在指向的位置。实现排序
(2)希尔排序
基本思想:先选定一个整数,把待排序文件中所有记录分成个组,所
有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达 =1 时,所有记录在统一组内排好序。
如图,将元素分组进行排序,将一组内较大的数向后交换,随着分组越来越小,数据越来越趋于前小后大,近似有序,
但是,要实现到完全有序,还需要直接插入排序进行排序,可以说,希尔排序是对直接插入排序的一个优化。
代码实现
public void shall(int[] array,int gap){
for (int i = gap; i < array.length; i++) {
int tmp = array[i];
int j = i-gap;
for (; j > 0; j -= gap) {
if(array[j] > tmp){
array[j + gap] = array[j];
}else{
break;
}
}
array[j + gap] = tmp;
}
}
public void shallSort(int[] array){
//对于希尔排序,就是将数组分为小组,小组数量由大到小不断改变并且排列
int gap = array.length;
while(gap > 1){
//每次进入循环将gap进行分割
gap /= 2;
shall(array,gap);
}
}
2.选择排序
(1)直接选择排序
基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
实现思路 1:
1.定义 i 下标遍历全部数组
2.定义 Min 下标记录比 i 小的值
3.j 从 i 后一个元素进行遍历,只要值比 Min 就修改 Min的值。
4.交换 i 和 Min 的值
代码实现
public void selectSort(int[] array){
for (int i = 0; i < array.length; i++) {
int Min = i;
for(int j = i + 1;j < array.length-i;j++){
if(array[j] < array[Min]){
//更新Min的值
Min = j;
}
}
if(i != Min){
swap(array,i,Min);
}
}
}
public void swap(int[] array,int i,int min){
int tmp = array[i];
array[i] = array[min];
array[min] = tmp;
}
实现思路 2
1.定义 left 和 right 分别在数组元素的最左和最右
2.定义一个 j 在两个之间进行遍历
3.将 j 的值分别与 left 和 right 比较,找到最大和最小的值和分别将下标赋给 min 和 max,直到相遇。
代码实现
public void selectSort2(int[] array){
int left = 0;
int right = array.length-1;
while(left < right){
int min = left;
int max = left;
for (int j = left+1; j <= right; j++) {
if(array[j] < array[min]){
min = j;
}
if(array[j] > array[max]){
max = j;
}
}
//将较小的元素放到左边
swap(array,left,min);
//排除最大值就是第一个元素的情况
if(max == left){
max = min;
}
//将较大的元素放到右边
swap(array,right,max);
left++;
right--;
}
}
代码分析:
- 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 稳定性:不稳定
(2)堆排序
注:这里如果不是很明白可以看看数据结构之优先级队列(堆)
基本思想:堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
实现思路:
因为我们要将建成的是一个升序数组,所以需要建立一个大根堆。
-
将数组以大根堆的形式实现。
-
实现一个向下转移的方法。
-
将堆中的元素进行交换实现排序。
代码实现:
public void heapSort(int[] array){
createBigHeap(array);
int end = array.length-1;
while(end > 0){
swap(array,end,0);
shiftDown(array,0,end);
end--;
}
}
private void createBigHeap(int array[]){
//找到子节点和父节点
for (int parent = (array.length-1-1)/2; parent >=0; parent--){
shiftDown(array,parent,array.length);
}
}
private void shiftDown(int[] array,int parent,int len) {
int child = (2 * parent) + 1;
while (child < len) {
//判断是否有有孩子,以及判断左右两个孩子的最大值
if (child + 1 < len && array[child] < array[child + 1]) {
child++;
}
if (array[child] > array[parent]) {
//这里交换孩子和父节点的位置
swap(array, parent, child);
//交换完毕后,还要判断后面还是否存在节点
parent = child;
child = 2 * parent + 1;
} else {
break;
}
}
}
代码分析:
- 堆排序使用堆来选数,效率就高了很多。
- 时间复杂度:O(N*logN)
- 空间复杂度:O(1)
- 稳定性:不稳定