1 快速排序
平均时间复杂度 O(nlogn)
,具体时间复杂度取决于递归时数组的分割比例 ,数组的分割比例取决于选择的基准 pivot
不稳定,相同值会产生交换 在大多数情况下都是适用的,尤其在数据量大的时候性能优越性更加明显
快速排序的退化和优化
两种退化的情况: 根本原因还是递归时数组分割的比例
数据正序或倒序时,每次选择首部的元素,退化成冒泡排序 O(n2)
可以先用O(n)
的时间复杂度检测是否有序 也可以三数取中法,或者随机选取 pivot
大量重复数据
每次划分为三个部分:大于、小于、等于 pivot
的三个部分,仅递归排序大于和小于 pivot
的两部分 优化
每次随机选取 privot
三数取中法选取 privot
,在大多数情况下都能较好地平衡划分(最坏的情况是当选定的三个元素恰好是最小、最大或完全相同,概率很低) 对于小规模子数组采用其他排序算法,如插入排序或选择排序等
private void binarySort ( int start, int end, int [ ] nums) {
if ( start >= end) {
return ;
}
int flag = nums[ start] ;
int l = start;
int r = end;
while ( l < r) {
while ( l < r && nums[ r] >= flag) {
r-- ;
}
if ( l >= r) {
break ;
} else {
nums[ l] = nums[ r] ;
}
while ( l < r && nums[ l] <= flag) {
l++ ;
}
if ( l >= r) {
break ;
} else {
nums[ r] = nums[ l] ;
}
}
nums[ l] = flag;
binarySort ( start, l - 1 , nums) ;
binarySort ( l + 1 , end, nums) ;
}
2 堆排序
时间复杂度 O(nlogn)
把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出,这个过程持续到剩余数只有一个时结束 不稳定 建立堆和调整堆的过程中会产生比较大的开销,在元素太少的时候并不适用
public int [ ] getLeastNumbers ( int [ ] arr, int k) {
int [ ] heap = new int [ k + 1 ] ;
int numsAdd = 0 ;
for ( int i = 0 ; i < k; i++ ) {
heap[ i + 1 ] = arr[ i] ;
numsAdd++ ;
int curr = numsAdd;
while ( curr / 2 > 0 && heap[ curr / 2 ] < heap[ curr] ) {
int temp = heap[ curr / 2 ] ;
heap[ curr / 2 ] = heap[ curr] ;
heap[ curr] = temp;
curr = curr / 2 ;
}
}
for ( int i = k; i < arr. length; i++ ) {
if ( arr[ i] < heap[ 1 ] ) {
heap[ 1 ] = heap[ k] ;
heap[ k] = - 1 ;
int curr = 1 ;
int l = 2 ;
int r = 3 ;
while ( ( l <= k && heap[ curr] < heap[ l] ) || ( r <= k && heap[ curr] < heap[ r] ) ) {
if ( r <= k && heap[ l] < heap[ r] ) {
int temp = heap[ curr] ;
heap[ curr] = heap[ r] ;
heap[ r] = temp;
curr = r;
} else {
int temp = heap[ curr] ;
heap[ curr] = heap[ l] ;
heap[ l] = temp;
curr = l;
}
l = 2 * curr;
r = l + 1 ;
}
heap[ k] = arr[ i] ;
curr = k;
while ( curr / 2 > 0 && heap[ curr / 2 ] < heap[ curr] ) {
int temp = heap[ curr / 2 ] ;
heap[ curr / 2 ] = heap[ curr] ;
heap[ curr] = temp;
curr = curr / 2 ;
}
}
}
return Arrays . copyOfRange ( heap, 1 , k + 1 ) ;
}
3.冒泡排序
时间复杂度O(n2)
完全反序时交换次数最多 在相邻元素相等时,它们并不会交换位置,所以冒泡排序是稳定排序
def bubble ( self, array, length) :
for i in range ( length - 1 , - 1 , - 1 ) :
for j in range ( i) :
if array[ j] > array[ j + 1 ] :
pre, post = array[ j + 1 ] , array[ j]
array[ j + 1 ] , array[ j] = post, pre
return array
4.选择排序
时间复杂度O(n2)
和冒泡排序有一定的相似度,可以认为选择排序是冒泡排序的一种改进 不稳定的排序算法
def select ( self, array, length) :
for i in range ( length - 1 ) :
min_val = array[ i]
min_idx = i
for j in range ( i + 1 , length) :
if min_val > array[ j] :
min_val = array[ j]
min_idx = j
array[ min_idx] = array[ i]
array[ i] = min_val
return array
5.插入排序
时间复杂度O(n2)
把待排序的数组分成已排序和未排序两部分,初始的时候把第一个元素认为是已排好序的。从第二个元素开始,在已排好序的子数组中寻找到该元素合适的位置并插入该位置
def insert ( self, array, length) :
for i in range ( 1 , length) :
val = array[ i]
ptr = i
while ( ptr > 0 and array[ ptr - 1 ] > val) :
array[ ptr] = array[ ptr - 1 ]
ptr -= 1
array[ ptr] = val
return array
6.归并排序【数组中逆序对的个数、合并K个升序链表、链表排序】
例题:数组中逆序对的个数
问题的关键在于合并两个数组时,每当右侧数组出一个元素,且左侧没有走完时,增加逆序对个数 画图解题
class Solution {
int reverseCount = 0 ;
public int reversePairs ( int [ ] nums) {
if ( nums. length <= 1 ) {
return reverseCount;
}
mergeSortAndCount ( nums, 0 , nums. length - 1 ) ;
return reverseCount;
}
private void mergeSortAndCount ( int [ ] nums, int start, int end) {
if ( start >= end) {
return ;
}
int mid = ( start + end) / 2 ;
mergeSortAndCount ( nums, start, mid) ;
mergeSortAndCount ( nums, mid + 1 , end) ;
int [ ] copy = Arrays . copyOfRange ( nums, start, end + 1 ) ;
int l = 0 ;
int r = mid - start + 1 ;
for ( int i = start; i <= end; i++ ) {
if ( l == mid - start + 1 ) {
nums[ i] = copy[ r] ;
r++ ;
} else if ( r == copy. length) {
nums[ i] = copy[ l] ;
l++ ;
} else {
if ( copy[ l] <= copy[ r] ) {
nums[ i] = copy[ l] ;
l++ ;
} else {
nums[ i] = copy[ r] ;
r++ ;
reverseCount += ( mid - start - l + 1 ) ;
}
}
}
}
}
class Solution {
public ListNode mergeKLists ( ListNode [ ] lists) {
ListNode head = new ListNode ( - 1 ) ;
ListNode curr = head;
Queue < ListNode > pq = new PriorityQueue < > ( ( a, b) -> a. val - b. val) ;
for ( int i = 0 ; i < lists. length; i++ ) {
if ( lists[ i] != null ) {
pq. offer ( lists[ i] ) ;
}
}
while ( pq. size ( ) > 0 ) {
ListNode node = pq. poll ( ) ;
curr. next = node;
curr = node;
node = node. next;
if ( node != null ) {
pq. offer ( node) ;
}
}
return head. next;
}
}
例题:排序链表
添加链接描述 快慢指针寻找链表中点,左右递归执行排序 合并排序结果
class Solution {
public ListNode sortList ( ListNode head) {
if ( head == null || head. next == null ) {
return head;
}
ListNode fast = head;
ListNode slow = head;
ListNode slowPrev = slow;
while ( fast != null && fast. next != null ) {
fast = fast. next. next;
slowPrev = slow;
slow = slow. next;
}
slowPrev. next = null ;
ListNode l = sortList ( head) ;
ListNode r = sortList ( slow) ;
ListNode dummy = new ListNode ( - 1 ) ;
ListNode curr = dummy;
while ( l != null && r != null ) {
if ( l. val <= r. val) {
curr. next = l;
curr = l;
l = l. next;
} else {
curr. next = r;
curr = r;
r = r. next;
}
}
curr. next = ( l == null ) ? r : l;
return dummy. next;
}
}
7.定制排序【合并区间、把数组排成最小的数】
对排序容器(例如 TreeSet, TreeMap
)的灵活运用 例题:合并区间
将每个 (from, to) 包装成 Turple 对象,放在 TreeSet 里,先按 from 后按 to 排序 遍历 TreeSet 执行合并
class Solution {
public int [ ] [ ] merge ( int [ ] [ ] intervals) {
Set < Turple > treeSet = new TreeSet < > ( ( a, b) -> {
if ( a. from == b. from) {
return a. to - b. to ;
}
return a. from - b. from;
} ) ;
for ( int i = 0 ; i < intervals. length; i++ ) {
treeSet. add ( new Turple ( intervals[ i] [ 0 ] , intervals[ i] [ 1 ] ) ) ;
}
List < Turple > resultList = new LinkedList < > ( ) ;
Iterator < Turple > iter = treeSet. iterator ( ) ;
int currFrom = - 1 ;
int currTo = - 1 ;
while ( iter. hasNext ( ) ) {
Turple t = iter. next ( ) ;
if ( t. from == currFrom || currTo >= t. from) {
currTo = Math . max ( t. to , currTo) ;
} else {
if ( currFrom != - 1 ) {
resultList. add ( new Turple ( currFrom, currTo) ) ;
}
currFrom = t. from;
currTo = t. to ;
}
}
resultList. add ( new Turple ( currFrom, currTo) ) ;
int [ ] [ ] resultArr = new int [ resultList. size ( ) ] [ 2 ] ;
for ( int i = 0 ; i < resultList. size ( ) ; i++ ) {
resultArr[ i] [ 0 ] = resultList. get ( i) . from;
resultArr[ i] [ 1 ] = resultList. get ( i) . to ;
}
return resultArr;
}
class Turple {
int from;
int to ;
public Turple ( int from, int to ) {
this . from = from;
this . to = to ;
}
}
}
例题:把数组排成最小的数
定制排序,对于两数 a 和 b,比较 ab 和 ba 的大小,选择较小的排序方法
class Solution {
public String minNumber ( int [ ] nums) {
int length = nums. length;
if ( length == 0 ) {
return "0" ;
}
List < Integer > list = Arrays . stream ( nums) . boxed ( ) . sorted ( ( a, b) -> {
String aa = a. toString ( ) ;
String bb = b. toString ( ) ;
String ab = aa + bb;
String ba = bb + aa;
for ( int i = 0 ; i < aa. length ( ) + bb. length ( ) ; i++ ) {
if ( ab. charAt ( i) > ba. charAt ( i) ) {
return 1 ;
} else if ( ab. charAt ( i) < ba. charAt ( i) ) {
return - 1 ;
}
}
return 0 ;
} ) . collect ( Collectors . toList ( ) ) ;
StringBuilder sb = new StringBuilder ( ) ;
for ( Integer num : list) {
sb. append ( num) ;
}
return sb. toString ( ) ;
}
}
8.拓扑排序【循环依赖问题】
课程表
class Solution {
public boolean canFinish ( int numCourses, int [ ] [ ] prerequisites) {
int [ ] inDegree = new int [ numCourses] ;
Set < Integer > validCourses = new HashSet < > ( ) ;
for ( int [ ] turple : prerequisites) {
inDegree[ turple[ 0 ] ] ++ ;
}
Queue < Integer > course0InDegree = new LinkedList < > ( ) ;
for ( int i = 0 ; i < numCourses; i++ ) {
if ( inDegree[ i] == 0 ) {
course0InDegree. offer ( i) ;
}
}
while ( course0InDegree. size ( ) > 0 ) {
int currCourse = course0InDegree. poll ( ) ;
if ( validCourses. contains ( currCourse) ) {
continue ;
}
validCourses. add ( currCourse) ;
for ( int [ ] turple : prerequisites) {
if ( turple[ 1 ] == currCourse) {
inDegree[ turple[ 0 ] ] -- ;
if ( inDegree[ turple[ 0 ] ] == 0 ) {
course0InDegree. add ( turple[ 0 ] ) ;
}
}
}
}
return validCourses. size ( ) == numCourses;
}
}