1.1 排序分类
内部排序和外部排序,前者数据加载到内存,后者数据量大需借助外部文件.
内部排序包含:
插入排序:直接插入排序,希尔排序
选择排序:简单选择排序,堆排序--------- 补充:堆排序
交换排序:冒泡排序,快速排序
归并排序
基数排序
1.2 复杂度
1)度量一个程序时间有两种方法,事后统计或事前估算,事前估算就需要分析时间复杂度
2)时间复杂度:算法中的基本操作语句的重复执行次数是问题规模 n 的某个函数,
计算方法:去常数阶–>保留最高阶项–>去除最高阶项系数
常见时间复杂度:常数阶 O(1)、对数阶 O(log2n)、线性阶 O(n)、线性对数阶 O(nlog2n)、平方阶 O(n^2)、立方阶 O(n^3)、k 次方阶 O(n^k)、指数阶 O(2^n)
对应结构:普通无循环结构语句、while循环、for循环、外层for内部while结构、2层for循环…
3)空间复杂度:度量程序占用的存储空间大小,比如基数排序的空间换时间
优化:
如果本轮没有进入交换操作的代码,说明数组已经有序,退出循环
private static void sort(int[] arr) {
int temp;
boolean flag = true;
for (int i = 0; i < arr.length - 1; i++) {//每完成一轮排序则排除一个最大值,剩余的是无序数组,注意区别插入排序中的交换法
for (int j = 0; j < arr.length-i-1; j++) {
if (arr[j] > arr[j + 1]) {
flag = false;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
System.out.println("完成第"+(i+1)+"轮排序");
print(arr);
//优化:如果本轮没有换位则无需再排
if (flag) {
break;
} else {
flag = true;
}
}
}
优化:
如果本轮的第一个元素就是最小值,则不用操作
private static void sort(int[] arr) {
int min = 0;
int cor=0;
for (int j = 0; j < arr.length - 1; j++) {
//每一轮都默认乱序数组第一个数为该轮最小值
min = arr[j];
cor=j;
for (int i = j; i < arr.length; i++) {
if (arr[i] < min) {
//找到更小的则更新最小值和角标
cor = i;
min = arr[i];
}
}
//最小值已保存,将第一个数与最小处赋值
if (cor!=j)//优化:第一个数为最小则无需赋值
arr[cor] = arr[j];
arr[j] = min;
print(arr);
}
}
(交换法)思路:
private static void sort(int[] arr) {
//变量声明在循环外更好
int insertVal =0;
int insertIndex= 0;
for (int i = 1; i < arr.length; i++) {
insertVal = arr[i];//取出待插入数,保证该位置可被后推覆盖
insertIndex= i-1;//有序数组下界
//索引不越界下,逆序遍历有序数组,只要待插入值比遍历值小(小到大排序)就将遍历值后推
while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
//位置找到,则先补上遍历附带的索引自减1,再将待插入值放入有序数组
//赋值优化:只有待插入数和有序数组之间已经有序则无需赋值,既然有序则没有上面的后推和遍历,
// 即索引自减没执行过,因此
if (insertIndex + 1 != i) {
arr[insertIndex+1] = insertVal;
System.out.print("该轮赋值了");
}
System.out.print("第"+i+"轮: ");
print(arr);
}
}
//等价于sort方法,只是for代替while循环,交换代替了插入的后推及赋值
//后续的希尔排序就是基于这两种方法的优化
private static void sort2(int[] arr) {
int temp = 0;
for (int i = 0; i < arr.length-1; i++) {
for (int j = i; j >= 0; j--) {
if (arr[j] > arr[j + 1]) {
temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
print(arr);
}
思路:
//参考插入排序的交换式,加入分组和步长的概念,不断缩小组内增量
private static void sort1(int[] arr) {
int temp = 0;
int count = 0;
for (int gap = arr.length/2; gap >0 ; gap/=2) {
for (int i = 0; i < arr.length - gap; i++) {//这里使用i++而不是加步长,才能保证每个组内排序
for (int j = i; j >=0; j-=gap) {
if (arr[j] > arr[j + gap]) {
temp = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = temp;
}
}
}
System.out.print("第"+(++count)+"轮");
print(arr);
}
}
//参考插入排序的移位法,加入分组和步长的概念,不断缩小组内增量
private static void sort2(int[] arr) {
int value = 0;
int key = 0;
int count = 0;
for (int gap = arr.length/2; gap >0 ; gap/=2) {
for (int i = gap; i < arr.length ; i++) {
value = arr[i];
key = i - gap;
while (key >= 0 && value < arr[key]) {
arr[key + gap] = arr[key];
key-=gap;
}
if (key != i - gap) {
arr[key + gap] = value;
}
}
System.out.print("第"+(++count)+"轮");
print(arr);
}
}
思路:
//分治方法,分为递归分组,治为该层的归并排序
private static void sort(int[] arr,int low ,int high) {
//辅助数组,用于复制归并后的有序数组
int[] assist = new int[arr.length];
//安全性校验,表示该组只有一个元素
if (high <= low) {
return;
}
//分治法中的分,即分组,也是递归调用,上面的健壮判断可以看成递归终止条件
int mid = low + ((high - low) >> 1);
sort(arr, low, mid);
sort(arr,mid+1,high);
//当分组完毕(递归终止)后,最终的两组内只有一个元素(即有序),才会执行下面代码,即层层排序返回上层,这里为界,上面为分组,下面为(归并)排序
//个人理解为:在上面的分组操作没有终止前,一直在做分的操作,直到分组完毕才开始治,治完再治上层
//举例:本层的上下界为0和1,则上面的分组防止直接不做操作返回,而对两个单元素的子数组做如下的归并排序,返回上层
//使用三个指针,表示两个子数组的起点索引和归并数组的起点索引
int i = low;
int p1 = low;
int p2 = mid + 1;
//比较子有序数组的起点值,不断复制最小值到辅助数组并后移指针,直到某一子数组复制完毕
while (p1 <= mid && p2 <= high) {
if (arr[p1] < arr[p2]) {
assist[i++] = arr[p1++];
} else {
assist[i++] = arr[p2++];
}
}
//将未复制完的子数组弄完
while (p1 <= mid) {
assist[i++] = arr[p1++];
}
while (p2 <= high) {
assist[i++] = arr[p2++];
}
//将归并后的顺序同步给请求者
for (int j = low; j <= high; j++) {
arr[j] = assist[j];
}
}
思路:
个人理解+优化:
//问题1:理想条件下,基准值两边交换完后,刚好剩一个小的与基准值交换,如果不剩呢
// 退出while前r指针做了自减,所以指向的一定是左子组的数据
//问题2:如果只有一侧数据呢,怎么交换
// 若只有右组数据,则r最终指向上界即自己换自己,若只有左组数据,l和r在下界相交,只需交换一次基准值即可
//问题3:分组结束的状况是怎么样的,也就是最底层和上两层的情况举例
// 最底层区间只有一个元素,上层则2/3个
//问题4:指针遇到与基准值等值的怎么办?
// 正常交换操作即可,交换后等基准值分到哪一组都不影响正确性
//问题5:什么情况下左右指针相遇?
// 左右指针相交,说明交换完毕/只有一侧有数据,需退出,否则持续交换
// 至于最终l==r还是l>r,看谁先找,以及是否能找到,结果复杂不好判断
//问题6:为什么指针相交后,基准值的交换操作用的是右指针r指的元素,而不是左指针?
// 由问题1知r最终指向左子组,即r的元素比基准值小,因此使用r和lo交换
//优化:
// 基准值的交换操作可以加判断条件,比如该组就两个数且有序,即lo==r,则不用交换
最终代码:
//问题1:理想条件下,基准值两边交换完后,刚好剩一个小的与基准值交换,如果不剩呢
// 退出while前r指针做了自减,所以指向的一定是左子组的数据
//问题2:如果只有一侧数据呢,怎么交换
// 若只有右组数据,则r最终指向上界即自己换自己,若只有左组数据,l和r在下界相交,只需交换一次基准值即可
//问题3:分组结束的状况是怎么样的,也就是最底层和上两层的情况举例
// 最底层区间只有一个元素,上层则2/3个
//函数功能:对数组的某一区间进行排序
private static void sort(int[] arr,int lo ,int hi) {
//安全性校验:保证区间内不止一个元素
if (lo >= hi) {
return;
}
//分组:将区间元素(按序)分为左右子组,并拿到基准值最终的角标
//左右指针最初为区间上界和下界+1,即待分组元素的边界外,开始相向移动
int l = lo;
int r = hi+1;
int temp = 0;
int pivot = arr[lo];//基准值
while (true) {
//右指针向左移,遇到左元素(基准值)停下,或遇到区间上界停下
//说明:如果指针遇到等基准值,则交换后等基准值分到哪一组都不影响正确性
while (arr[--r] > pivot) {
if (r == lo) {
break;
}
}
while (arr[++l] < pivot) {
if (l == hi) {
break;
}
}
//左右指针相交,说明交换完毕/只有一侧有数据,需退出,否则持续交换
//至于最终l==r还是l>r,看谁先找,以及是否能找到,结果复杂不好判断
if (l >= r) {
break;
} else {
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
}
}
//使用r作为基准值角标是,因为r最终指向左子组元素
//当然r也可能是lo上界(本身有序没有交换操作),因此可做优化if(lo===r)
temp = arr[lo];
arr[lo] = arr[r];
arr[r] = temp;
//左右子组排序
sort(arr, lo, r-1);
sort(arr, r+1, hi);
}
private static void sort(int[] arr) {
//准备工作:
// 1.开辟一个二维数组(10个桶),每个桶大小和arr保持一致
// 2.计算数组中最大位数
int[][] buckets = new int[10][arr.length];
int max = Integer.MIN_VALUE;
for (int i = 0; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
int rounds = (max + "").length();
//元素最高n位,就需要进行n轮排序,依次对个位,十位,百位操作
for (int round = 0,n=1; round < rounds; round++,n*=10) {
//每一轮需先重置桶偏移量
int[] bucketOffset = new int[10];
//遍历数组元素,计算桶编号,放入对应桶中并更新桶偏移量
for (int i = 0; i < arr.length; i++) {
int bucketNo = arr[i]/n % 10;
buckets[bucketNo][bucketOffset[bucketNo]] = arr[i];
bucketOffset[bucketNo]++;
}
//顺序遍历每个桶中元素,并同步到原数组
int index = 0;
for (int i = 0; i < 10; i++) {
if (bucketOffset[i] != 0) {
for (int j = 0; j < bucketOffset[i]; j++) {
arr[index++] = buckets[i][j];
}
}
}
System.out.print("第" + (round+1) + "轮:");
print(arr);
}
}