作者是一名工作不久的实习生,以下代码是目前理解能力的最大优化,希望对你有帮助;作者在快速排序与堆排序的对比之下,优先推荐读者使用堆排序,理由是堆排序的代码复杂度是固定的,要排序的数越多越有优势,在我看完,你会发现堆排序的代码比快速排序更好理解,且代码更少。
这里的每种排序都有递归与非递归两种排序方式(简单的连注释都不需要)
1.二分查找排序算法(折半查找)
理解:只能对已排好序的数组进行排序,升序与降序皆可。这里以升序为例,它的用法可顾名思义,每次对比都是从中间的值对比,等于中间值返回提示,大于则往右半部分查找,重复中间值对比;小于则往左查找,重复中间值对比,最后查没查到值都返回提示。
public static void main(String[] args) {
int[] n = {1,2,3,4,5,6,7,8,9,10};
int number = 10;
//递归
//System.out.println(binarySearch(n,0,n.length-1,number));
//非递归
System.out.println(noRecursion(n,0,n.length-1,number));
}
//递归
static String binarySearch(int[] n,int start, int end,int number){
int index = (start+end)/2;
if (n[index] == number){
return "你查找的值在第"+(index+1)+"位";
}
if (start == end){
return "没有你要找的值";
}
if (n[index] > number){
end = index-1;
}
if (n[index] < number){
start = index+1;
}
return binarySearch(n,start,end,number);
}
//非递归
static String noRecursion(int[] n,int start, int end,int number){
String hint = "";
while (start <= end){
int index = (start+end)/2;
if (n[index] == number){
hint = "你查找的值在第"+(index+1)+"位";
break;
}
if (start == end){
hint = "没有你要找的值";
break;
}
if (n[index] > number){
end = index-1;
}
if (n[index] < number){
start = index+1;
}
}
return hint ;
}
2.冒泡排序
理解:冒泡排序是快速排序的源,理解了冒泡排序对快速排序会有很大的帮助。冒泡排序只能与相邻的值对比,比如你拿了3位置的值,你就只能与1或2位置的值进行比较替换(比较方向只能一个,哪个方向由自己决定),这里已向右比为例:
public static void main(String[] args) {
int[] n = {56, 7, 23, 235, 45, 321, 45, 324, 46, 23, 0};
//递归
//bubble(n);
//非递归
noRecursion(n);
System.out.println("排序后"+ Arrays.toString(n));
}
//递归
static void bubble(int[] n){
int staus = 0;
for (int i = 0; i<n.length-1;i++){
if (n[i] > n[i+1]){
int temp = n[i];
n[i] = n[i+1];
n[i+1] = temp;
staus = 1;
}
}
if (staus == 0){
return;
}
bubble(n);
}
//非递归
static void noRecursion(int[] n){
int staus = 1;
while (staus == 1){
int k = 0;
for (int i = 0; i<n.length-1;i++){
if (n[i] > n[i+1]){
int temp = n[i];
n[i] = n[i+1];
n[i+1] = temp;
k = 1;
}
}
staus = k;
}
}
3.快速排序
理解:快速排序那肯定是快的很了,网上说快速排序是目前最受欢迎的排序方式,但我不管它受不受欢迎,只知道它的代码量比堆排序多几行。目前网上很多的快速排序都只有递归的方法教程,在这里我再弄了个非递归的,所以希望正在看的你都能理解完,面试的时候就跟面试官说会两种方法,问他想要哪种,B格高不高,你自己度量。
这里快速排序的思路是先拿第一个位置的值做裁判,比裁判小的放到左边,比裁判大的放到右边,再分别拿大的和小的重复上述操作:
public static void main(String[] args) {
int[] n = {56, 7, 23, 235, 45, 321, 45, 324, 46, 23, 0,1,320,89};
//递归
//sort(n,0, n.length-1);
//非递归
noRecursion(n);
System.out.println("排序后:"+ Arrays.toString(n));
}
//递归
static void sort(int[] n,int start,int end){
if (start >= end){
return;
}
int left = start;
int right= end;
int temp = n[start];
//这个while只是为了让当前第一个值先与当前最后的值进行对比
while (right != left){
while (right != left && n[right] > temp){
right --;
}
if (right != left){
n[left] = n[right];
left ++;
}
while (right != left && n[left] < temp){
left ++;
}
if (right != left){
n[right] = n[left];
right --;
}
}
n[left] = temp;
sort(n,start,left -1);
sort(n,left+1,end);
}
//非递归
static void noRecursion(int[] n){
List<Map<String,Integer>> list = new ArrayList<>();
Map<String,Integer> m = new HashMap<>();
m.put("left",0);
m.put("right",n.length-1);
list.add(m);
for (int i = 0; i < list.size();i++){
int left = list.get(i).get("left");
int right = list.get(i).get("right");
if (left >= right){
continue;
}
int indexStart = left;
int indexEnd = right;
int start = n[left];
//这个while只是为了让当前第一个值先与当前最后的值进行对比
while (left != right){
while(left != right && n[right] > start){
right --;
}
if (left != right){
n[left] = n[right];
left ++;
}
while(left != right && n[left] < start ){
left ++;
}
if (left != right){
n[right] = n[left];
right --;
}
}
n[left] = start;
Map<String,Integer> ms = new HashMap<>();
ms.put("left",indexStart);
ms.put("right",left-1);
list.add(ms);
Map<String,Integer> mm = new HashMap<>();
mm.put("left",left+1);
mm.put("right",indexEnd);
list.add(mm);
}
}
4.插入排序
理解:用哪个排序都不要用这个排序,纯粹是用来搞笑的,只要知道有这回事就行。
它搞笑的理由是:每次先找到最小的值,插入到当前的第一个位置,然后在第一个位置的值与它所在位置之间的值还要一个一个的往后移,你没看错,是一个一个的往后移:(补充,在同学的提示下,发现我所理解的插入排序与官方有出入,故此算法只做参考)
public static void main(String[] args) {
int[] n = {56, 7, 23, 235, 45, 321, 45, 324, 46, 23, 0,1};
//递归
//sort(n,0);
//非递归
noRecursion(n);
System.out.println("排序后:"+ Arrays.toString(n));
}
//递归
static void sort(int[] n,int start){
int index = start;
int status = n[start];
for (int i = start;i < n.length;i++){
if (n[i] < status){
index = i;
status = n[i];
}
}
int temp = n[index];
for (int i = index;i > start;i--){
n[i] = n[i-1];
}
n[start] = temp;
if (start == n.length-2){
return;
}
start++;
sort(n,start);
}
//非递归
static void noRecursion(int[] n){
int index = 0;
while (index < n.length-1) {
int start = n[index];
int end = index;
for (int i = index;i < n.length;i++){
if (n[i] < start){
end = i;
start = n[i];
}
}
int temp = n[end];
for (int i = end;i > index;i--){
n[i] = n[i-1];
}
n[index] = temp;
index++;
}
}
5.选择排序
理解:选择排序比插入排序好一点点,建议依然是会用就行。它的用法是,先找到最小的值与第一个位置的值互换位置,再找到次小的值与第二个位置的值互换位置:
public static void main(String[] args) {
int[] n = {56, 7, 23, 235, 45, 321, 45, 324, 46, 23, 0,1};
//递归
//selectionSort(n,0);
//非递归
sort(n);
System.out.println("排序后:"+ Arrays.toString(n));
}
//递归
static void selectionSort(int[] n,int index){
int temp = index;
for (int i = index+1;i < n.length;i++){
if (n[i] < n[temp]){
temp = i;
}
}
if (temp == index){
return;
}
int end = n[temp];
n[temp] = n[index];
n[index] = end;
index++;
selectionSort(n,index);
}
//非递归
static void sort(int[] n){
int index = 0;
while (index < n.length){
int start = n[index];
int end = index;
for (int i = index;i < n.length;i++){
if (start > n[i]){
end = i;
start = n[i];
}
}
if (index == n.length-1){
break;
}
if (index < n.length){
int temp = n[index];
n[index] = n[end];
n[end] = temp;
index++;
}
}
}
6.堆排序
理解:压轴排序,号称高大上的排序方法,作者力荐,理解了这个就相当于理解了二叉树,而且我的代码比目前网上搜索优先推荐的代码的量都少。一定一定要掌握。我的用法是:用大根堆搞升序,把数组当成一颗树,第一个是根节点,第二个是第一个的左节点,第三个是第一个的右节点。第四个是第二个的左节点,依次类推。让每一个子节点与父节点对比,大的当节点,一轮下来就能让最大的值移到根节点的位置,再把根节点的值与当前最后一个值的位置替换,就能得出升序排列。(补充,在同学的测试下,我这算法的效率太差,于是呼,我改动了算术逻辑,改为先构造大根堆,后再与第二大根堆进行比较,算法效率倍增,因java没有代码解决递归的栈溢出问题,这里就不写递归方法了)
public static void main(String[] args) {
//Integer[] n = {56, 7, 23, 235, 45, 321};
List<Integer> list = new ArrayList<Integer>(60000);
int i = 0;
while (i < 60000){
list.add(new Random().nextInt(10000));
i++;
}
Integer[] n = list.toArray(new Integer[60000]);
long startTime = System.currentTimeMillis();
//重写方法
noRecursion(n);
long endTime = System.currentTimeMillis();
System.out.println("算法耗时"+(endTime - startTime));
System.out.println("排序后:"+ Arrays.toString(n));
}
/*方法重写*/
static void noRecursion(Integer[] n){
int start = 0;
int end = n.length-1;
/*先构造大根堆*/
bigStart(n,start,end);
end --;
while (start != end){
secondBase(n,start,end);
end --;
}
}
/*构造大根堆*/
static void bigStart(Integer[] n,int start,int end){
for (int i = end; i > start;i--){
if (n[i] > n[(i-1)/2]){
int temp = n[(i-1)/2];
n[(i-1)/2] = n[i];
n[i] = temp;
}
}
int temp = n[start];
n[start] = n[end];
n[end] = temp;
end --;
}
/*构造完大根堆后,再跟第二大的大根堆进行比较*/
static void secondBase(Integer[] n,int start,int end){
int index = start;
while (index * 2 +1 < end){
if (index * 2 + 2 < end) {
/*左根堆*/
if (n[index * 2 + 1] > n[index * 2 + 2]) {
int temp = n[index * 2 + 1];
n[index * 2 + 1] = n[index];
n[index] = temp;
index = index * 2 + 1;
}
}
if (index * 2 + 2 < end){
/*右根堆*/
if (n[index * 2 + 1] < n[index * 2 + 2]){
int temp = n[index * 2 + 2];
n[index * 2 + 2] = n[index];
n[index] = temp;
index = index * 2 + 2;
}
}
if (index *2 +1 < end){
if (n[index] < n[index * 2 + 1]){
int temp = n[index];
n[index] = n[index * 2 + 1];
n[index * 2 + 1] = temp;
}else {
break;
}
}
}
if (n[start] > n[end]){
int temp = n[start];
n[start] = n[end];
n[end] = temp;
}
}
7.希尔排序
希尔排序是插入排序的改进版,相比于插入排序,希尔排序的间隔对比步长更长,大幅度的步长对比能有效的减少对比的次数及元素移动的次数。
这里的使用方法:步长的长度gap先为数组长度除2,后面步长依次折半,这的使用方法在gap远小于数组长度时更明显,会有两层循环,第一层循环的值会在第二层优先循环完每一gap加或减index值。
代码送上:
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>(60000);
int i = 0;
while (i < 60000){
list.add(new Random().nextInt(10000));
i++;
}
Integer[] n = list.toArray(new Integer[60000]);
long startTime = System.currentTimeMillis();
shell(n);
long endTime = System.currentTimeMillis();
System.out.println("耗时:"+(endTime - startTime));
System.out.println("排序后:"+ Arrays.toString(n));
}
static void shell(Integer[] n){
int gap = n.length/2;
while (gap != 0){
for (int i = gap;i - gap >= 0 && i < n.length;i++){
int index = i;
int temp = n[i];
while (temp < n[index - gap]){
n[index] = n[index - gap];
index -= gap;
if (index - gap <= 0){
break;
}
}
n[index] = temp;
}
gap = gap/2;
}
}
8.归并排序
归并排序对比了几个网上的代码,及算法动图,发现只是靠动图来理解这个算法会很难,动图里大都是先拆分再合并,我也试了先拆分再合并效率太低。于是乎我换了一种思路,拆分的目的是为了把数组拆分到最小值(就是最后每一个元素都是单独的),既然是这样,那我为何不先把数组的每一个元素遍历到集合里存在,后面再从集合里取数组进行合并,这样的方式,用代码实现会简单很多。在这里我没有用递归,因为递归在出现栈溢出的问题。
代码送上:
public static void main(String[] args) {
List<Integer> lists = new ArrayList<Integer>(70000);
int i = 0;
while (i < 70000){
lists.add(new Random().nextInt(1000));
i++;
}
Integer[] n = lists.toArray(new Integer[70000]);
//Integer[] n = {56, 7, 23, 235, 45, 321, 45, 324, 46, 23, 0,1,3,899,67,67};
long startTime = System.currentTimeMillis();
List<Integer[]> list = new ArrayList<>();
split(n,list);
while (list.get(0).length != n.length){
list = test02(list);
}
long endTime = System.currentTimeMillis();
System.out.println("耗时:"+(endTime - startTime));
n = list.get(0);
System.out.println("排序后:"+ Arrays.toString(n));
}
//把每一个元素都拆分成数组
static void split(Integer[] n,List<Integer[]> list){
for (Integer i : n){
Integer[] integers = new Integer[1];
integers[0] = i;
list.add(integers);
}
}
//把每一层处理后的数组合并
static List<Integer[]> test02(List<Integer[]> list){
List<Integer[]> listCopy = new ArrayList<>();
for (int i = 0; i < list.size();i += 2) {
Integer[] left = list.get(i);
Integer[] right = new Integer[0];
if (i + 1 < list.size()){
right = list.get(i+1);
}
Integer[] full = new Integer[left.length + right.length];
dispose(full, left, right);
listCopy.add(full);
}
return listCopy;
}
//处理两个数组的方法
static void dispose(Integer[] full,Integer[] left,Integer[] right){
int fullIndex = 0;
int index = 0;
for (int i = 0;i < left.length;i++){
for (int j = index;j < right.length;j++){
if (left[i] <= right[j]){
full[fullIndex] = left[i];
fullIndex ++;
if (i == left.length-1){
for (int jj = j;jj < right.length;jj++){
full[fullIndex] = right[jj];
fullIndex ++;
}
}
break;
}
if (left[i] > right[j]){
full[fullIndex] = right[j];
fullIndex ++;
index ++;
continue;
}
}
if (index > right.length - 1){
full[fullIndex] = left[i];
fullIndex ++;
}
}
}
9.最后再赠送附上一道兔子生小孩的题目:
问题:有一只兔子,从第3个月开始每月生1只兔子,小兔子长到第3个月开始每个月也会生1只兔子,假如兔子都不死,问每个月的兔子总数是多少?
说明:但你理解的上面的排序方法后,会发现这道题无比简单。
代码送上:
public static void main(String[] args) {
int month = 20;//月数
//递归
//int s = sum(month);
//非递归
int s = noRecursion(month);
System.out.println(s);
}
//递归
static int sum(int month){
if (month == 1 || month == 2){
return 1;
}
return sum(month-1)+sum(month -2);
}
//非递归
static int noRecursion(int month){
int[] stage = new int[month];
for (int i = 0;i < month;i++){
if (i <= 1){
stage[i] = 1;
}else {
stage[i] = stage[i-1] + stage[i-2];
}
}
return stage[month - 1];
}
注:希望对各位有帮助。