最近跟着韩顺平老师的Java版数据结构,也把排序这一大块讲完了,排序也算是学习算法的基础,很多人对算法的兴趣也是从了解排序算法开始的。以下是我听完课后对几个常用排序算法的简单介绍,思路及代码实现。
注意:本文中排序的最终结果是升序,从小到大。
目录
一、冒泡排序
介绍:非常的经典,必会排序算法之一。
思路:如其名,就像水底的气泡一个一个向上冒,从下标较小的元素开始,逐步比较它相邻元素的值,如果发现前面比后面大,就交换位置,然后继续比较,最终大的数到后边。然后进行新一轮的比较。
代码实现:
public void bubbleSort(int[] array){
//倒数第二趟走完的时候,已经排序完毕了,最后一轮不用进行。所以是 array.length-1
for (int i= 0;i< array.length-1;i++){
//每一趟,将目前最大的放到最后。
int temp = 0; //用来交换时
boolean flag = false; //退出标识
//每一轮都筛出来过一个最大的了并且放在了最后面,所以就应该-i。
for (int j=0; j< array.length-1-i;j++){
//如果前面的数比后面大,就交换
if (array[j] > array[j+1]) {
flag = true;
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
//如果一轮里发现一次也没有交换,说明已经有序了,不用再循环了。
if (!flag){
break;
}
}
}
二、选择排序
介绍:与冒泡不同,冒泡一轮中可能交换很多次,选择一轮中只交换一次。交换次数较少。
思路:规定一个数是最小数,然后进入循环中,查看有没有数字比规定的最小数还小,如果有就重新确定最小值并且记录这个数字的位置,等该轮循环结束后交换。总共需要比较 arr.length-1轮。
代码实现:
public void selectSort(int[] arr){
for (int i = 0;i<arr.length-1;i++){
//每一轮都假定,最小值和最小值下标的位置。因为i前面的都已经是最小。所以就从i开始。
int minIndex = i; //最小值的索引位置
int min = arr[i]; //最小值
//从i + 1开始找最小值的位置
for (int j =i + 1;j<arr.length;j++){
//先找位置
if (min>arr[j]){
minIndex = j;
min = arr[j]; //重置最小值
}
}
//循环结束就交换
arr[minIndex] = arr[i];
arr[i] = min;
}
}
三、插入排序
介绍:就是把数组里的数,找位置插入,形成有序数组
思路:目标排序数组分成一个有序表(只有一个元素)和无序表(剩余元素)。从无序表中每一轮都拿出一个数和有序表中的元素比较,确定位置,插入成为新的有序表。
代码实现:
public void insertSort(int[] arr) {
//从1开始,因为arr[0]是有序的,要从1及后面的数组中把数据依次加入到有序数组中来。
for (int i = 1; i < arr.length; i++) {
//定义待插入的数
int insertVal = arr[i];
//定义待插入的位置,即arr[i]的前面这个数的下标
int insertIndex = i - 1;
//给insertVal找到插入的位置
//insertIndex >= 0 保证insertVal找插入位置,不越界
//insertVal < arr[insertIndex] 说明待插入的数找到位置了,但是前面数还没动
while (insertIndex >= 0 && insertVal < arr[insertIndex]){
//这里是在给待插入的数腾位置
arr[insertIndex + 1] = arr[insertIndex];
insertIndex --;
}
//当退出while循环时,说明插入位置找到,位置是insertIndex + 1
//这里是把待插入的数放到腾出的位置上。
arr[insertIndex + 1] = insertVal;
}
}
四、希尔排序
介绍:插入排序有弊端在,当最小的数放在了目标排序数组的最后,插入排序需要挪很多个位置来放最小的数。希尔排序解决了这种问题。
思想:按照一定的增量将数组平均分成几个组,然后针对这几个组进行插入排序,通过不断减小增量,分的组越来越少,最终增量为1,分成了一个组。此时数组就有序了。
代码实现;
public void ShellSort(int[]arr){
//gap 增量
for (int gap = arr.length / 2; gap > 0; gap/=2){
//从第gap个元素开始,逐个对其所在的组进行直接插入
for (int i = gap; i < arr.length; i++) {
int insertIndex = i - gap; //要插入的位置
int insertVal = arr[i]; //要插入的值
while(insertIndex >=0 && insertVal < arr[insertIndex]) {
arr[insertIndex + gap] = arr[insertIndex];
insertIndex -= gap;
}
arr[insertIndex + gap] = insertVal;
}
}
}
五、快速排序
介绍:对冒泡排序的一种改进,非常常用的一种排序算法,当数据大量重复的时候不太友好。
思路:找一个基准值,然后按照左边都小于基准值,右边都大于基准值的规则来排序。当一轮排序完成后,会根据条件从左右半边都进行递归。最终使得整个数组变有序。
代码实现:
//固定基准数法 基准数在中间,交换类型
public static void quickSortTest(int[]arr,int start,int end){
//定义一个基数
int pivot = arr[(end-start)/2+start];
int left = start;
int right = end;
int temp = 0;
//条件不满足后就说明一轮排序结束了,要左右进行递归了
while(left < right){
//从右向左找,直到找到比pivot小的数
while(arr[right] > pivot){
right--;
}
//从左向右找,直到找到比pivot大的数
while(arr[left] < pivot){
left++;
}
//指针重合了,直接跳出。
if (left == right){
break;
}
//找到就换
temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
//换完考虑left 和 right 的数是否和pivot相等,防止死循环
if (arr[left] == pivot){
right--;
}
if (arr[right] == pivot){
left++;
}
}
//这个条件必须有,上面的循环还有可能出现left>right,是left=0和right=1 的情况,而arr[left]=pivot的情况,当这个情况出现时,right--,arr[right]=pivot,left又++,导致left>right;此时不满足这个条件。
if (left == right){
left += 1;
right -= 1;
}
//一轮排序完,直接进行下一轮
if (start < right){
quickSortTest(arr,start,right);
}
if (end > left){
quickSortTest(arr,left,end);
}
}
六、归并排序
介绍:采用分治的思想,先把数组分分分,然后根据一定的顺序合起来。归并排序时间复杂度是线性的,是稳定排序算法。
思路:先把目标排序数组一轮一轮拆分,最终拆成剩下一个元素,然后开始合并,合并过程需要一个中间下标和中间数组接收,然后比较两个序列下标对应值的大小来确定加入中间数组的顺序。直到把两个序列里的数据全部读取完,然后将中间数组拷贝到目标排序数组。自此排序结束。
代码实现:
//分+合方法
public static void mergeSort(int[]arr,int left,int right,int[] temp){
if (left < right) {
int mid = (right - left)/2 + left; //中间的索引
//向左递归进行分解
mergeSort(arr,left,mid,temp);
//右分解
mergeSort(arr,mid+1,right,temp);
//合并
merge(arr,left,mid,right,temp);
}
}
//合并的方法
/**
*
* @param arr 排序的原始数组
* @param left 左边有序序列的初始索引
* @param mid 中间索引
* @param right 右边索引
* @param temp 做中转的数组
*/
public static void merge(int[]arr,int left,int mid,int right, int[]temp) {
int i = left; //表示左边有序序列的初始索引
int j = mid + 1; //表示右边有序序列的初始索引
int t = 0; //指向temp数组的当前索引,中转数组的下标
//先把左右两边数据(已经有序)按规则填充到temp数组中,直到两边有序序列有一边处理完毕为止
while(i <= mid && j <= right) { //终止条件
if(arr[i] < arr[j]){
temp[t++] = arr[i++];
} else {
temp[t++] = arr[j++];
}
}
//把有剩余数据的一边的数据依次全部填充到temp去
while(i <= mid){ //说明左边有序序列还有剩余的元素,全部填充到temp
temp[t++] = arr[i++];
}
while(j <= right){ //说明右边有序序列还有剩余的元素,全部填充到temp
temp[t++] = arr[j++];
}
//将temp数组的元素拷贝到arr
//并不是每次都拷贝所有的元素,前面递归的不是所有
t = 0; //temp数组索引挪到开始位置,准备赋值了
while(left <= right){ //第一次合并,templeft = 0 而right = 1 最后一次templeft = 0 而left = 7
arr[left++] = temp[t++];
}
}