二分查找:
算法思想:又叫折半查找,要求待查找的序列有序。每次取中间位置的值与待查关键字比较,如果中间位置的值比待查关键字大,则在前半部分循环这个查找的过程,如果中间位置的值比待查关键字小,则在后半部分循环这个查找的过程。直到查找到了为止,否则序列中没有待查的关键字。
优点是比较次数少,查找速度快,平均性能好;
其缺点是要求待查表为有序表,且插入删除困难。
因此,折半查找方法适用于不经常变动而查找频繁的有序列表。
使用条件:查找序列是顺序结构,有序。
public class BinarySearch { public static void main(String[] args){ int[] arr = {1,3,5,7,9,11,13}; int key = 3; // int position = commonBinarySearch(arr,key); int position = commonBinarySearch2(arr,key,0,arr.length -1); if (position == -1){ System.out.println("查找的是"+key+",序列中没有该数!"); }else { System.out.println("查找的是"+key+",找到位置为:"+position); } } //采用while循环实现 public static int commonBinarySearch(int[] arr,int key){ int low = 0; int high = arr.length - 1; int middle = 0; if (key < arr[low] || key > arr[high] || low > high){ return -1; } while (low<=high){ middle = (low + high)/2; if (arr[middle]>key){ //比关键字大则关键字在左区域 high = middle -1; }else if (arr[middle] < key){ //比关键字小则关键字在右区域 low = middle + 1; }else { return middle; } } return -1; //如果都没找到则返回-1 } //采用递归方法实现 public static int commonBinarySearch2(int[] arr,int key,int low,int high){ if (key < arr[low] || key > arr[high] || low > high){ return -1; } int middle = (low + high)/2; if (arr[middle] > key){ //比关键字大则关键字在左区域 return commonBinarySearch2(arr,key,low,middle - 1); }else if (arr[middle] < key){ //比关键字小则关键字在右区域 return commonBinarySearch2(arr,key,middle + 1,high); }else { return middle; } } }
顺序查找
一、顺序查找:
a) 原理:顺序查找就是按顺序从头到尾依次往下查找,找到数据,则提前结束查找,找不到便一直查找下去,直到数据最后一位。
b) 图例说明: 原始数据:int[] a={4,6,2,8,1,9,0,3}; 要查找数字:8
public class SequelSearch { public static void main(String[] args){ int[] a = {4,6,2,8,1,9,0,3}; Scanner input = new Scanner(System.in); System.out.println("请输入要查找的数字:"); int num = input.nextInt(); int result = search(a,num); if (result == -1){ System.out.println("你输入的数字不存在"); }else{ System.out.println("你输入的数字存在,位置是"+(result + 1)+"个"); } } public static int search(int[] a,int num){ for (int i = 0;i < a.length; i++){ if (a[i] == num){ return i; } } return -1; } }
分块查找:
分块查找要求把一个数据分为若干块,每一块里面的元素可以是无序的,但是块与块之间的元素需要是有序的。(对于一个非递减的数列来说,第i块中的每个元素一定比第i-1块中的任意元素大)
public class BlockSearch { public static void main(String[] args){ int[] index = {10,20,30}; BlockSearch blocksearch=new BlockSearch(index); blocksearch.insert(1); blocksearch.insert(12); blocksearch.insert(22); blocksearch.insert(9); blocksearch.insert(18); blocksearch.insert(23); blocksearch.insert(5); blocksearch.insert(15); blocksearch.insert(27); blocksearch.printAll(); System.out.println(blocksearch.search(18)); System.out.println(blocksearch.search(29)); } private int[] index;//建立索引 private ArrayList[] list; //初始化索引表 public BlockSearch(int[] index){ if (index != null && index.length != 0){ this.index = index; this.list = new ArrayList[index.length]; for (int i = 0;i < list.length; i ++){ list[i] =new ArrayList(); //初始化容器 } }else { throw new Error("index cannot be null"); } } public void insert(int value){ int i = binarySearch(value); list[i].add(value); } private int binarySearch(int value){ int start=0; int end=index.length; int mid= -1; while(start<=end) { mid=(start+end)/2; if(index[mid]>value) { end=mid-1; } else { //如果相等,也插入后面 start=mid+1; } } return start; } /** * 查找元素 * @param data * @return */ public boolean search(int data) { int i=binarySearch(data); for(int j=0;j<list[i].size();j++) { if(data==(int)list[i].get(j)) { System.out.println(String.format("查找元素为第: %d块 第%d个 元素", i+1,j+1)); return true; } } return false; } /** * 打印每块元素 * */ public void printAll(){ for (int i = 0;i<list.length;i++){ ArrayList l = list[i]; System.out.print("ArrayList"+ i +":"); for (int j = 0; j<l.size();j++){ System.out.print(l.get(j)+" "); } } } }
快速排序:
可参照该博客进行理解:https://blog.csdn.net/as02446418/article/details/47395867
public class QuickSort { public static void quickSort(int[] arr,int low,int high){ int i,j,temp,t; if(low>high){ return; } i=low; j=high; //temp就是基准位 temp = arr[low]; while (i<j) { //先看右边,依次往左递减 while (temp<=arr[j]&&i<j) { j--; } //再看左边,依次往右递增 while (temp>=arr[i]&&i<j) { i++; } //如果满足条件则交换 if (i<j) { t = arr[j]; arr[j] = arr[i]; arr[i] = t; } } //最后将基准为与i和j相等位置的数字交换 arr[low] = arr[i]; arr[i] = temp; //递归调用左半数组 quickSort(arr, low, j-1); //递归调用右半数组 quickSort(arr, j+1, high); } public static void main(String[] args){ int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19}; quickSort(arr, 0, arr.length-1); for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } }
选择排序:
1、基本思想:
选择排序是一种简单直观的排序算法,其基本原理如下:对于给定的一组记录,经过第一轮比较后得到最小的记录,然后将该记录的位置与第一个记录的位置交换;接着对不包括第一个记录以外的其他记录进行第二次比较,得到最小记录并与第二个位置记录交换;重复该过程,知道进行比较的记录只剩下一个为止。
2、复杂度分析:
从简单选择排序的过程来看,它最大的特点是交换移动数据次数相当少,这样就节约了相应的时间。分析它的时间复杂度发现,无论是最好最差情况,其比较次数都是一样多,第 i 趟排序需要进行 n-i 次关键字比较,此时需要比较次,对于交换次数而言,当最好的时候,交换0次,最差的时候,也就是初始降时,交换次数为 n-1 次,基于最终的时间排序与交换次数总和,因此,总的时间复杂度依然为。
尽管与冒泡排序同为,但简单选择排序的性能要优于冒泡排序。
public static void selectSort(int[] a){ if (a == null || a.length < 0){ return; } for (int i = 0; i < a.length; i++){ int temp = a[i]; int flag = i;// 将当前下标定义为最小值下标 for (int j = i + 1; j < a.length;j++){ if (a[j] < temp){// a[j] < temp 从小到大排序;a[j] > temp 从大到小排序 temp = a[j]; flag = j;// 如果有小于当前最小值的关键字将此关键字的下标赋值给flag } } if (flag != i ){ a[flag] = a[i]; a[i] = temp; } } } public static void main(String[] args) { int a[] = { 49,38,65,97,76,13,27,49 }; selectSort(a); System.out.println(Arrays.toString(a)); }
堆排序
1、基本思想
堆是一种特殊的树形数据结构,其每个节点都有一个值,通常提到的堆都是指一颗完全二叉树,根结点的值小于(或大于)两个子节点的值,同时,根节点的两个子树也分别是一个堆。
堆排序就是利用堆(假设利用大顶堆)进行排序的方法。它的基本思想是,将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的 n-1 个序列重新构造成一个堆,这样就会得到 n 个元素中次大的值。如此反复执行,便能得到一个有序序列了。
堆排序的实现需要解决的两个关键问题:
(1)将一个无序序列构成一个堆。
(2)输出堆顶元素后,调整剩余元素成为一个新堆。
2、复杂度分析
堆排序的运行时间主要耗费在初始构建堆和在重建堆时反复筛选上。在构建对的过程中,因为我们是完全二叉树从最下层最右边的非终端节点开始构建,将它与其孩子进行比较和若有必要的互换,对每个非终端节点来说,其实最多进行两次比较和互换操作,因此整个构建堆的时间复杂度为O(n)。
在正式排序时,第i次取堆顶记录重建堆需要用O(logi)的时间(完全二叉树的某个节点到根节点的距离为),并且需要取n-1次堆顶记录,因此,重建堆的时间复杂度为O(nlogn)。
所以总体来说,堆排序的时间复杂度为O(nlogn),由于堆排序对原始记录的状态并不敏感,因此它无论是最好、最坏和平均时间复杂度均为O(nlogn)。这在性能上显然要远远好过于冒泡、简单选择、直接插入的时间复杂度了。
空间复杂度上,它只有一个用来交换的暂存单元,也非常的不错。不过由于记录的比较与交换是跳跃式进行的,因此堆排序也是一种不稳定的排序方法。
另外,由于出事构建堆所需要的比较次数比较多,因此,他并不适合待排序序列个数较少的情况。
3.大根堆排序算法的基本操作
① 初始化操作:将R[1..n]构造为初始堆;
②每一趟排序的基本操作:将当前无序区的堆顶记录R[1]和该区间的最后一个记录交换,然后将新的无序区调整为堆(亦称重建堆)。
注意:
①只需做n-1趟排序,选出较大的n-1个关键字即可以使得文件递增有序。
②用小根堆排序与利用大根堆类似,只不过其排序结果是递减有序的。堆排序和直接选择排序相反:在任何时刻堆排序中无序区总是在有序区之前,且有序区是在原向量的尾部由后往前逐步扩大至整个向量为止。
public class HeapSort1 { //构建大根堆:将array看成完全二叉树的顺序存储结构 public int[] builderMaxHeap(int[] array){ //从最后一个节点array.length-1的父节点(array.length-1-1)/2开始,直到根节点0,反复调整堆 for (int i = (array.length-2)/2;i >= 0;i--){ adjustDownToUp(array,i,array.length); } return array; } //将元素array[k]自下往上逐步调整树形结构 private void adjustDownToUp(int[] array, int k, int length) { int temp = array[k]; //i为初始化为节点k的左孩子,沿节点较大的子节点向下调整 for (int i = 2*k+1;i<length-1;i = 2*i+1){ //取节点较大的子节点的下标 if (i + 1<length && array[i] < array[i + 1]){ i ++;//如果节点的右孩子>左孩子,则取右孩子节点的下标 } if (temp >= array[i]){//根节点 >=左右子女中关键字较大者,调整结束 break; }else {//根节点 <左右子女中关键字较大者 array[k] = array[i]; k = i;//【关键】修改k值,以便继续向下调整 } } array[k] = temp;//被调整的结点的值放人最终位置 } //堆排序 public int[] heapSort(int[] array){ array = builderMaxHeap(array);//初始建堆,array[0]为第一趟值最大的元素 for (int i = array.length - 1;i>=1;i--){ int temp = array[0];//将堆顶元素和堆低元素交换,即得到当前最大元素正确的排序位置 array[0] = array[i]; array[i] = temp; adjustDownToUp(array,0,i);//整理,将剩余的元素整理成堆 } return array; } //删除堆顶元素操作 public int[] deleteMax(int[] array){ //将堆的最后一个元素与堆顶元素交换,堆底元素值设为-99999 array[0] = array[array.length - 1]; array[array.length - 1] = -99999; //对此时的根节点进行向下调整 adjustDownToUp(array,0,array.length); return array; } //插入操作:向大根堆array中插入数据data public int[] insertData(int[] array,int data){ array[array.length - 1] = data;//将新节点放在堆的末端 int k = array.length - 1;//需要调整的节点 int parent = (k-1)/2;//双亲节点 while (parent >=0 && data > array[parent]){ array[k] = array[parent];//双亲节点下调 k = parent; if (parent != 0){ parent = (parent-1)/2;//继续向上比较 }else {//根节点已调整完毕,跳出循环 break; } } array[k] = data;//将插入的结点放到正确的位置 return array; } public void toString(int[] array){ for(int i:array){ System.out.print(i+" "); } } public static void main(String args[]){ HeapSort1 hs = new HeapSort1(); int[] array = {87,45,78,32,17,65,53,9,122,155,22,34,56,32,888}; System.out.print("构建大根堆:"); hs.toString(hs.builderMaxHeap(array)); System.out.print("\n"+"删除堆顶元素:"); hs.toString(hs.deleteMax(array)); System.out.print("\n"+"插入元素63:"); hs.toString(hs.insertData(array, 63)); System.out.print("\n"+"大根堆排序:"); hs.toString(hs.heapSort(array)); } }
插入排序
1、基本思想:每步将一个待排序的记录,按其顺序码大小插入到前面已经排序的字序列的合适位置(从后向前找到合适位置后),直到全部插入排序完为止。
public static void insertSort(int[] numbers){ int temp = 0; int j = 0; for (int i = 0; i<numbers.length;i++){ j = i; temp = numbers[i]; while (j>0 && temp < numbers[j-1]){ numbers[j] = numbers[j-1]; j -- ; } numbers[j] = temp; } } public static void main(String[] args){ InsertSort s = new InsertSort(); int[] nums = {3,5,2,1,7,9,10}; s.insertSort(nums); System.out.println(Arrays.toString(nums)); }
效率分析
稳定
空间复杂度O(1)
时间复杂度O(n2)
最差情况:反序,需要移动n*(n-1)/2个元素
最好情况:正序,不需要移动元素
数组在已排序或者是“近似排序”时,插入排序效率的最好情况运行时间为O(n);
插入排序最坏情况运行时间和平均情况运行时间都为O(n2)。
希尔排序:(不稳定)
复杂度分析:
希尔排序的关键并不是随便分组后各自排序,而是将相隔某个“增量”的记录组成一个子序列,实现跳跃式移动,使得排序的效率提高。需要注意的是,增量序列的最后一个增量值必须等于1才行。另外,由于记录是跳跃式的移动,希尔排序并不是一种稳定的排序算法。
希尔排序最好时间复杂度和平均时间复杂度都是,最坏时间复杂度为。
public static void shellSort(int[] num){ int j = 0; int temp = 0; for (int increment = num.length/2;increment > 0; increment /= 2 ){ System.out.println("increment:" + increment); for (int i = increment;i< num.length;i++){ temp = num[i]; for (j = i - increment;j >= 0;j -= increment){ if (temp < num[j]){ num [j + increment] = num[j]; }else{ break; } } //之前j执行了j-=increment,所以这里执行的j+increment=j=0 num[j+increment] = temp; } for (int i = 0;i<num.length;i++){ System.out.print(num[i]+" "); } } } public static void main(String[] args){ int[] num ={3,4,2,1,77,9,10}; shellSort(num); }
冒泡排序:
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
public class BubbleSort { public static void main(String[] args){ int[] array = {2,6,1,45,3,9,10}; //BubbleSort.BubbleSort1(array); BubbleSort.BubbleSort2(array); for (int num : array){ System.out.print(num +","); } } public static void BubbleSort1(int[] array){ /* for (int i =0; i < array.length -1 ; i ++){ for (int j = i + 1; j < array.length ; j++){ if (array[j] < array[i]){ int temp = array[i]; array[i] = array[j]; array[j] = temp; } } }*/
int temp = 0; int size = numbers.length; for(int i = 0 ; i < size-1; i ++) { for(int j = 0 ;j < size-1-i ; j++) { if(numbers[j] > numbers[j+1]) //交换两数位置 { temp = numbers[j]; numbers[j] = numbers[j+1]; numbers[j+1] = temp; } } }}
归并排序
1、基本思想:
归并排序就是利用归并的思想实现的排序方法。而且充分利用了完全二叉树的深度是的特性,因此效率比较高。其基本原理如下:对于给定的一组记录,利用递归与分治技术将数据序列划分成为越来越小的半子表,在对半子表排序,最后再用递归方法将排好序的半子表合并成为越来越大的有序序列。
经过第一轮比较后得到最小的记录,然后将该记录的位置与第一个记录的位置交换;接着对不包括第一个记录以外的其他记录进行第二次比较,得到最小记录并与第二个位置记录交换;重复该过程,知道进行比较的记录只剩下一个为止。
2、复杂度分析
一趟归并需要将数组 a[]中相邻的长度为h的有序序列进行两两归并.并将结果放到temp[]中,这需要将待排序列中的所有记录扫描一遍,因此耗费O(n),而又完全二叉树的深度可知,整个归并排序需要进行()次,因此总的时间复杂度为O(nlogn),而且这是归并排序算法中最好、最坏、平均的时间性能。
由于归并排序在归并过程中需要与原始序列同样数量的存储空间存放归并结果以及递归时深度为的栈空间,因此空间复杂度为O(n+logn).
另外,对代码进行仔细研究,发现merge函数中有if (a[i] < a[j]) 的语句,说明它需要两两比较,不存在跳跃,因此归并排序是一种稳定的排序算法。
也就是说,归并排序是一种比较占内存,但却效率高且稳定的算法。
3、工作原理:
(1)申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
(2)设定两个指针,最初位置分别为两个已经排序序列的起始位置
(3)比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
(4)重复步骤3直到某一指针达到序列尾
(5)将另一序列剩下的所有元素直接复制到合并序列尾
4、归并排序过程如下:
以数组{50,10,90,30,70,40,80,60,20}为例,
public class MergeSort { public static void merge(int[] a, int low, int mid, int high) { int[] temp = new int[high - low + 1]; int i = low;// 左指针 int j = mid + 1;// 右指针 int k = 0; // 把较小的数先移到新数组中 while (i <= mid && j <= high) { if (a[i] < a[j]) { temp[k++] = a[i++]; } else { temp[k++] = a[j++]; } } // 把左边剩余的数移入数组 while (i <= mid) { temp[k++] = a[i++]; } // 把右边边剩余的数移入数组 while (j <= high) { temp[k++] = a[j++]; } // 把新数组中的数覆盖nums数组 for (int k2 = 0; k2 < temp.length; k2++) { a[k2 + low] = temp[k2]; } } public static void mergeSort(int[] a, int low, int high) { int mid = (low + high) / 2; if (low < high) { // 左边 mergeSort(a, low, mid); // 右边 mergeSort(a, mid + 1, high); // 左右归并 merge(a, low, mid, high); System.out.println(Arrays.toString(a)); } } public static void main(String[] args) { int a[] = { 51, 46, 20, 18, 65, 97, 82, 30, 77, 50 }; mergeSort(a, 0, a.length - 1); System.out.println("排序结果:" + Arrays.toString(a)); }