利用相邻两个元素的比较,将较大的元素放在后面,这样经过一轮比较排在最后的元素就是最大的元素。依次比较n-1次,就可以得到一个升序的数组。
方式一:
// 传入一个需要排序的数组 public void sort(int[] array){ // 每一个元素都与其前面一个元素进行比较,如果前一个元素比后一个元素大, // 则交换位置,经过一轮循环后,数组最后一个元素就是最大的元素。 // 故此处有end--,即表示无需每次遍历整个数组 for(int end = array.length - 1; end > 0; end--){ // 从索引为1的元素开始,与前一个元素进行比较 for(int begin = 1; begin <= end; begin++){ // 如果前一个元素比其大,则交换 if(array[begin] < array[begin - 1]){ int temp = array[begin]; array[begin] = array[begin - 1]; array[begin - 1] = temp; } } } }优化方式一: (使用一个标记符,判断数组中的元素是否已经有序)
public void sort(int[] array){ for(int end = array.length - 1; end > 0; end--){ boolean sorted = true; // 用于判断此后的数组是否有序 for(int begin = 1; begin <= end; begin++){ // 如果进入该交换语句中,表示数组还是无序 if(array[begin] < array[begin - 1]){ int temp = array[begin]; array[begin] = array[begin - 1]; array[begin - 1] = temp; sorted = false; } } if(sorted){// 如果一轮循环后,没有一次进入交换语句中,表示该数组已经有序,无需继续排序 break; } } }优化方式二: (利用一个变量,用来记录数组最后一次交换顺序的索引)
public void sort(int[] array){ for(int end = array.length - 1; end > 0; end--){ int sortIndex = 0;// 用于记录,数组最后一次执行交换顺序的位置 for(int begin = 1; begin <= end; begin++){ // 如果进入该交换语句中,表示数组还是无序 if(array[begin] < array[begin - 1]){ int temp = array[begin]; array[begin] = array[begin - 1]; array[begin - 1] = temp; sortIndex = begin;// 进入交换语句,则更新sortIndex } } end = sortIndex;// 提前终止for循环 } }最坏、平均时间复杂度: O ( n 2 ) O(n^2) O(n2);最好时间复杂度: O ( n ) O(n) O(n);
空间复杂度: O ( 1 ) O(1) O(1);
属于稳定排序。
遍历一遍数组,找到最大值,与数组末尾元素交换位置。
最坏、平均、最好时间复杂度: O ( n 2 ) O(n^2) O(n2);
空间复杂度: O ( 1 ) O(1) O(1);
注:1.与冒泡排序相比,大大减小了交换的次数,平均性能优于冒泡排序。
2.可通过堆来选择最大值,实现对选择排序的优化。
3.属于不稳定排序。
采用数据结构堆来进行排序。
1.将数组元素建堆,最大堆(升序)。
2.然后交换堆顶元素与尾元素,对此时的堆顶元素进行一次下滤操作,最后将堆大小减一。
3.循环操作,直到堆中只有一个元素。此时表示原数组以及排好序。
最好、最坏、平均复杂度: O ( n l o g n ) O(nlogn) O(nlogn);
空间复杂度: O ( 1 ) O(1) O(1);
属于不稳定排序。
数组的左边存放已经有序的元素。将数组右边的元素依次与左边的元素进行比较,插入到合适的位置。
方式一:
public void sort(int[] array){ for(int begin = 1; begin < array.length; begin++){ int cur = begin;// 表示当前要插入的元素的索引 while(cur > 0 && array[cur] < array[cur - 1]){ int temp = array[cur]; array[cur] = array[cur - 1]; array[cur - 1] = temp; cur--; } } }优化一: (先保存要插入的元素,找到该元素的位置后再进行赋值操作)——用“挪动”来替换“交换”
public void sort(int[] array){ for(int begin = 1; begin < array.length; begin++){ int cur = begin;// 表示当前要插入的元素的索引 int element = array[cur]; while(cur > 0 && element < array[cur - 1]){ array[cur] = array[cur - 1]; cur--; } array[cur] = element;// 最后进行赋值 } }利用二分搜索进行优化:
// 假设array数组为一个全局变量,可以在某个类中定义,然后通过构造器传入一个真正的数组 public void sort(){ for(int i = 1; i < array.length; i++){ insert(i, search(i)); } } // 将指定索引的元素插入到数组的指定位置中 public void insert(int source, int dest){ int element = array[source]; for(int i = source; i > dest; i--){ array[i] = array[i - 1]; } array[dest] = element; } // 使用二分搜索,查找指定元素在有序数组中的位置 public int search(int index){ int begin = 0;// 查找范围的起始位置为0 int end = index;// 查找范围的终止位置为index while(begin < end){// 当begin和end相同时,表示已经遍历完所有的元素 int mid = (end - begin) >> 1;// 中点 if(array[index] < array[mid]){ end = mid; } else { begin = mid + 1; } } return begin;// 返回查找到的位置 }最坏、平均时间复杂度: O ( n 2 ) O(n^2) O(n2);
采用二分搜索的方式进行优化只是减少了比较的次数
最好时间复杂度: O ( n ) O(n) O(n);
空间复杂度: O ( 1 ) O(1) O(1);
属于稳定排序
采用分治策略——先将规模较大的模块分成相同的两部分,分别对两部分进行处理,最后合并。
其中,如果分的两部分的规模还是很大,继续进行进行划分。
循环一下步骤:
1.将数组进行二分处理;
直到数组中不可再分(只有一个元素)。
2.对数组的两部分分别进行排序处理;
3.合并数组。
最坏、最好、平均时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn);
空间复杂度: O ( n ) O(n) O(n);
属于稳定排序。
先找到一个轴点(pivot),然后让轴点两边的元素满足:
1.左边的元素都是小于轴点元素;
2.右边的元素都是大于轴点元素;
3.与轴点元素相等的元素随便放哪边。
4.为了让轴点元素合理,使用随机数来生成轴点元素。
最坏时间复杂度: O ( n 2 ) O(n^2) O(n2);
最好、平均时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn);
空间复杂度: O ( l o g n ) O(logn) O(logn);
属于稳定排序。
将数组看成一个矩阵,利用步长序列来确定将数组分成几列来处理。对每一列进行排序,当步长序列最终为一列时,该数组已经排好序。
步长序列:是一组数值(降序排列),代表的是当前操作需要将数组分为几列来进行排序,最终步长值为1时,此时只有一列已经拍好序。
步长序列:有很多种,但是本文只提及两种——由希尔本人设计的shellStepSequence和目前性能最好的sedgewickStepSequence。
1.希尔本人设计的步长序列:
s t e p = n / 2 k 其 中 k 为 1 , 2 , 3... ( 直 到 s t e p 为 1 ) \quad\quad\quad\quad\quad\quad step = n / 2^k \quad\quad 其中k为1,2,3...(直到step为1) step=n/2k其中k为1,2,3...(直到step为1)
2.目前性能最好的步长序列:
s t e p = { 9 ∗ ( 2 k − 2 k 2 ) + 1 , k = e v e n 8 ∗ 2 k − 6 ∗ 2 k + 1 2 + 1 , k = o d d \quad \quad \quad \quad\quad \quad step=\begin{cases} 9*(2^k - 2^\frac{k}{2}) + 1,k=even\\ 8*2^k - 6*2^\frac{k + 1}{2} + 1, k=odd\end{cases} step={9∗(2k−22k)+1,k=even8∗2k−6∗22k+1+1,k=odd
步长序列:
// 步长序列生成函数——传入数组的长度 public List<Integer> sedgewickStepSequence(int length){ List<Integer> list = new ArrayList<>(); int k = 0, step = 0; while(true){ int pow1 = (int)Math.pow(2, k >> 1); if((k & 1) == 1){// 偶数 step = 9 * pow1 * (pow1 - 1) + 1; } else { int pow2 = (int)Math.pow(2, (k + 1) >> 1); step = 8 * pow1 * pow1 - 6 * pow2 + 1; } if(step >= length){ break; } list.add(0, step); k++; } return list; } public List<Integer> shellStepSequence(int length){ List<Integer> list = new ArrayList<>(); while((length >>= 1) > 0){ list.add(length); } return list; }最好时间复杂度: O ( n ) O(n) O(n)
希尔给出的步长序列的最坏时间复杂度: O ( n 2 ) O(n^2) O(n2)
Robert Sedgewick给出的步长序列的最坏时间复杂度: O ( n 4 3 ) O(n^\frac{4}{3}) O(n34)
属于稳定排序。
计算每个元素在数组中的出现个数,然后推断出其在数组中的索引。
最好、最坏、平均时间复杂度: O ( n + k ) O(n + k) O(n+k)
空间复杂度: O ( n + k ) O(n + k) O(n+k)
属于稳定排序
1.生成一定数量的桶(可用链表或者动态数组);
2.按照一定的规则,将元素均匀分配到桶数组中;
3.分别对每个桶进行排序;
4.合并非空的桶为有序数组。
时间复杂度: O ( n ) + m ∗ O ( n m l o g n m ) = O ( n + n l o g n m ) = O ( n + n l o g n − n l o g m ) = O ( n + k ) 其 中 k = n l o g n − n l o g m O(n) + m * O(\frac{n}{m}log\frac{n}{m}) = O(n + nlog\frac{n}{m}) = O(n + nlogn - nlogm) =O(n + k) \quad 其中k = nlogn - nlogm O(n)+m∗O(mnlogmn)=O(n+nlogmn)=O(n+nlogn−nlogm)=O(n+k)其中k=nlogn−nlogm;
空间复杂度: O ( n + m ) m 为 桶 的 数 量 O(n + m) \quad m为桶的数量 O(n+m)m为桶的数量;
属于稳定排序。
依次对个位、十位、百位……进行排序(从低位到高位)
由于每一位只能取0~9共十位,故可用计数排序进行排序操作。
方式一:
// 假设array数组为一个全局变量,可以在某个类中定义,然后通过构造器传入一个真正的数组 public void sort(){ // 先找到最大值,以找到该基数排序的最大范围 int max = array[0]; for(int i = 1; i < array.length; i++){ if(max < array[i]){ max = array[i]; } } int[] output = new int[array.length];// 用来存储排好序的数组 int[] counts = new int[10];// 每一位只有十种取值 for(int divider = 1; divider <= max; divider *= 10){ // 对每一位进行排序 countSort(divider, output, counts); } // 此处可对数组进行操作。如:返回或者直接在此处打印。 // 如果要对数组进行返回操作,则要将返回值类型变成int[] } // 计数排序 public void countSort(int divider, int[] output, int[] counts){ for(int i = 0; i < array.length; i++){ counts[i] = 0; } // 得到该位(个位、十位……)上每一个数(0~9)的数量 for(int i = 0; i < array.length; i++){ counts[array[i] / divider % 10]++; } for(int i = 1; i< array.length; i++){ counts[i] += counts[i - 1]; } for(int i = array.length - 1; i >= 0; i--){ output[--counts[array[i] / divider % 10]] = array[i]; } for(int i = 0; i < array.length; i++){ array[i] = output[i]; } }方式二: 利用桶的概念!
public void sort(int[] array){ int max = array[0]; for(int i = 1; i < array.length; i++){ if(max < array[i]){ max = array[i]; } } int[][] buckets = new int[10][array.length];// 桶数组 int[] bucketSize = new int[buckets.length];// 保存每个桶中元素的数量 for(int divider = 1; divider <= max; divider *= 10){ for(int i = 0; i < array.length; i++){ int no = array[i] / divider % 10;// 得到每个元素的位(个位、十位……)值 buckets[no][bucketSize[no]++] = array[i];// 根据位值,将元素添加到桶数组的相应位置 } int index = 0; for(int i = 0; i < buckets.length; i++){// 根据每个元素的位值,依次添加桶数组中的元素到原数组中 for(int j = 0; j < bucketSize[i]; j++){// 每个位值的元素个数。 array[index++] = buckets[i][j]; } bucketSize[i] = 0; } } // 此处可对数组进行操作。如:返回或者直接在此处打印。 // 如果要对数组进行返回操作,则要将返回值类型变成int[] }方式一:
最好、最坏、平均时间复杂度: O ( d ∗ ( n + k ) ) d 为 最 大 值 的 位 数 , k 为 进 制 数 O(d * (n + k)) \quad d为最大值的位数,k为进制数 O(d∗(n+k))d为最大值的位数,k为进制数
空间复杂度: O ( n + k ) k 为 进 制 数 O(n + k) \quad k为进制数 O(n+k)k为进制数
属于稳定排序。
方式二:
最好、最坏、平均时间复杂度: O ( d n ) O(dn) O(dn)
空间复杂度: O ( k n + n ) k 为 进 制 数 , d 为 最 大 值 的 位 数 O(kn + n) \quad k 为进制数,d为最大值的位数 O(kn+n)k为进制数,d为最大值的位数
属于稳定排序。