数据结构与算法(7)——排序算法
1.冒泡排序
小结:
(1). 一共进行数组的大小-1次大的循环
(2).每一趟排序的次数在逐渐的减少
(3).如果我们发现在某趟排序中,没有发生一次交换,则提前结束冒泡排序(优化)
代码实现:
public static void bubbleSort(int[] arr){
//冒泡排序,时间复杂度为O(n^2)
int temp = 0;//临时变量
boolean flag = false;//标识变量,表示是否进行过交换
for (int i = 0;i < arr.length-1;i++){
for (int j = 0;j < arr.length-1-i;j++){
//如果前面的数比后面的数大则交换
if (arr[j] > arr [j+1]){
flag = true;//进行过交换
temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
// System.out.println("第"+(i+1)+"趟排序后的数组");
// System.out.println(Arrays.toString(arr));
if (!flag){//在一趟排序中一次交换都没有发生过
break;
}else {
flag = false;//重置flag,进行下一次判断
}
}
}
2.选择排序
选择排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置后达到排序的目的
说明:
1.选择排序一共有数组大小-1轮排序
2.每一轮排序又是一个循环,循环的规则
2.1先假定当前这个数是最小数
2.2然后和后面的每一个数进行比较,如果发现有比当前数更小的数,就重新确定这个最小数,并得到下标
2.3当遍历完数组的最后时,就得到了本轮循环的最小数及其对应下标
2.4交换
代码实现:
//选择排序
public static void selectSort(int[] arr){
for (int i = 0;i<arr.length-1;i++){//一共要进行length-1次
int min = arr[i];//开始排序前将未排序的数的第一个假定为最小
int minIndex = i;//假定最小值所对应的数组索引
for (int j = i+1;j < arr.length;j++){
if (min > arr[j]){//找最小值及其对应索引
min = arr[j];
minIndex = j;
}
}
if(minIndex!=i){//如果找到的最小值索引不是第一个数,那么交换
arr[minIndex] = arr[i];
arr[i] = min;
}
}
}
3.插入排序
插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。
//插入排序
public static void insertSort(int[] arr){
int insertVal;
int insertIndex;
for (int i =0 ;i < arr.length-1;i++){
//定义待插入的数
insertVal = arr[i+1];
insertIndex = i;
//给insertVal找到插入的位置
while (insertIndex >= 0 && insertVal < arr[insertIndex]){//保证在给insertVal找插入位置时不越界
arr[insertIndex+1] = arr[insertIndex];
insertIndex--;
}
//退出while循环时,插入位置找到 insertIndex+1
if (insertIndex+1 != i){
arr[insertIndex+1] = insertVal;
}
System.out.println("第"+(i+1)+"轮插入结果:");
System.out.println(Arrays.toString(arr));
}
}
4.希尔排序
对于插入排序,(加入按照从小到大的顺序排列)若有一个很小的数在数列的靠后位置,这种情况下,后移的次数明显增多,对效率有影响。
希尔排序是插入排序的改进版本
4.1 希尔排序(交换法)代码实现
//希尔排序(交换法)
public static void shellSort(int[] arr){
int temp = 0;
int count = 0;
for (int gap = arr.length / 2 ;gap > 0;gap /= 2 ){
for (int i = gap;i < arr.length;i++){//每组的第二个数开始遍历
//遍历各组中所有的元素(共gap组,步长gap)
for (int j = i - gap;j >= 0;j=j-gap){
//如果当前这个元素大于加上步长后的那个元素,说明交换
if (arr[j] > arr[j+gap]){
temp = arr[j];
arr[j] = arr[j+gap];
arr[j+gap] = temp;
}
}
}
count++;
System.out.println("希尔排序第"+count+"轮之后"+ Arrays.toString(arr));
}
4.2 希尔排序(移位法)代码实现
//对交换式的希尔排序进行改进————————》》》》》移位法
public static void shellSort2(int[] arr){
for (int gap = arr.length/2;gap > 0;gap /= 2 ){
for (int i = gap;i < arr.length;i++){
int j = i;
int temp = arr[j];
if (arr[j] < arr[j - gap]){
while (j-gap >=0 && temp < arr[j-gap]){
//移动
arr[j] = arr[j - gap];
j=j-gap;
}
//当退出while循环找到temp插入的位置
arr[j] = temp;
}
}
}
}
5.快速排序
先把数组中的一个数当作基准数,一般会把数组中最左边的数当作基准数,然后从两边进行检索。先从右边检索比基准数小的。再从左边检索比基准数大的。如果检索到了,就停下,然后交换这两个元素。然后再继续检索。此时数组被基准数分为两部分,左边比基准数小,右边比基准数大。第一轮排序结束。再分别把左边和右边当作两个新数组,再以第一轮排序方式进行第二轮排序,知道整个数组排序完成。
代码实现
public static void quickSort(int[] arr,int left,int right){
//进行判断,如果左边索引比右边大,不合法
if (left > right ){
return;
}
//定义变量保存基准数
int base = arr[left];
//定义变量i,指向最左边
int i = left;
//定义变量j,指向最右边
int j = right;
//当i和j不相遇,在循环中检索
while (i != j){
//先由j从右往左检索比基准数小的,检索到比基准数小的就停下
while (arr[j] >= base && i < j){
j--; //j从右往左移动
}
while (arr[i] <= base && i < j){
i++; //j从左往右移动
}
//代码运行到此处代表i j 都停下了,交换i和j位置的元素
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
//如果上面循环条件不满足,说明i j 相遇了,就交换基准数和相遇位置的元素
arr[left] = arr[i];
arr[i] = base;
//基准数在这里就归为了左边的数比他小,右边的数比他大
//排基准数左边
quickSort(arr,left,i-1);
//排基准数右边
quickSort(arr,i+1,right);
}
6.归并排序
代码实现:
public class MergeSort {
public static void main(String[] args) {
int arr[] = {8,4,5,7,1,3,6,2,9};
int temp[] = new int[arr.length];
mergeSort(arr,0,arr.length-1,temp);
System.out.println(Arrays.toString(arr));
}
//数组的分解+合并的方法
public static void mergeSort(int[] arr,int left,int right,int[] temp){
if (left < right){
int mid = (left + right)/2; //中间索引
//向左递归分解
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;//初始化i,左边有序序列的初始索引
int j = mid + 1;//初始化j,表示右边有序序列的初始索引
int t = 0;//指向temp数组的当前索引
//先把左右两边的数据填充到temp数组,直到左右两边的有序序列有一边处理完毕为止
while (i <= mid &&j <= right){
if (arr[i] <= arr[j]){ //左边的数填充到临时数组
temp[t] = arr[i];
t++;
i++;
}else {
temp[t] = arr[j]; //将右边序列的当前元素填充到temp数组
t++;
j++;
}
}
//把有剩余数据的一边的数据依次全部填充到temp
while ( i <= mid){
temp[t] = arr[i];
t++;
i++;
}
while (j <= right) {
temp[t] = arr[j];
t++;
j++;
}
//将temp数组拷贝到arr,并不是每次都拷贝所有元素
t = 0;
int templeft = left;
while (templeft <= right){
arr[templeft] = temp[t];
t++;
templeft++;
}
}
}
7.基数排序
代码实现:
//基数排序方法
public static void radixSort(int[] arr){
//根据推导过程,我们可以得到最终的基数排序的代码
//1.得到数组中最大的数的位数
int max = arr[0];
for (int i = 0; i<arr.length;i++){
if (arr[i] > max){
max = arr[i];
}
}
//得到最大数是几位数
int maxLength = (max+"").length();
//定义一个二维数组,表示10个桶,每个桶就是一个一维数组
/*说明
1.二维数组包含10个一维数组
2.为了在放入数的时候数据溢出,则每个一维数组的大小定义为arr.length
*/
int[][] bucket = new int[10][arr.length];
//为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶每次放入的数据的个数
int[] bucketElementCounts = new int[10];
//使用循环将代码处理
for (int i= 0,n = 1;i < maxLength;i++,n *= 10){
//针对每个元素的对应的位进行排序
for (int j = 0;j < arr.length;j++){
//q取出每个元素的个位的值
int digitOfElement = arr[j] / n % 10;
//放入到对应的桶中
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement]++;
}
//按照这个桶的顺序放入原来数组
int index = 0;
for (int k = 0;k < bucketElementCounts.length;k++ ){
//如果桶中有数据,才放入原数组
if (bucketElementCounts[k] != 0){
//循环该桶即第k个一维数组
for(int l = 0;l < bucketElementCounts[k];l++){
//取出元素放到arr
arr[index] = bucket[k][l];
index++;
}
}
bucketElementCounts[k] = 0;
}
System.out.println("第"+(i+1)+"轮排序的结果"+ Arrays.toString(arr));
}
}