Skip to content

Commit e50d401

Browse files
committed
Java
1 parent 719876a commit e50d401

20 files changed

Lines changed: 5873 additions & 329 deletions

docs/AimForOffer/算法思想相关/2_排序.md

Lines changed: 3 additions & 323 deletions
Original file line numberDiff line numberDiff line change
@@ -1,326 +1,6 @@
11
# 排序
22

3-
## 1、常见的简单排序算法
4-
5-
### I. 选择排序
6-
7-
选择排序思路:**选择出数组中的最小元素,将它与数组的第一个元素交换位置。 再从剩下的元素中选择出最小的元素,将它与数组的第二个元素交换位置。 不断进行这样的操作,直到将整个数组排序。**
8-
9-
```java
10-
public void sort(int[] arr){
11-
int N = arr.length;
12-
int minIndex = -1;
13-
for(int i=0;i<N;i++){ // arr[i] 是当前元素
14-
minIndex=i;
15-
for(int j=i+1;j<N;j++){ //arr[j] 当前元素后面的元素
16-
if(arr[j]<arr[minIndex]){
17-
minIndex=j;
18-
}
19-
}
20-
if(minIndex==i){ //TODO:这里有个小优化:如果当前元素是最小的则不用交换了
21-
continue;
22-
}
23-
swap(arr,minIndex,i);
24-
}
25-
}
26-
27-
private void swap(int[] arr,int i,int j){
28-
int tmp = arr[i];
29-
arr[i] = arr[j];
30-
arr[j] = tmp;
31-
}
32-
33-
@Test
34-
public void test(){
35-
int[] a={3,5,2,4,1,0,-3};
36-
PrintArr.printArray(a);
37-
sort(a);
38-
PrintArr.printArray(a);
39-
}
40-
```
41-
42-
选择排序需要 ~N2/2 次比较和 ~N 次交换,它的运行时间与输入无关,这个特点使得它对一个已经排序的数组也需要这么多的比较和交换操作。
43-
44-
45-
46-
### II. 冒泡排序
47-
48-
冒泡排序思路:**从左到右不断交换相邻逆序的元素,在一轮的循环之后,可以让未排序的最大元素上浮到右侧。在一轮循环中,如果没有发生交换,就说明数组已经是有序的,此时可以直接退出。**
49-
50-
```java
51-
public void sort(int[] arr){
52-
for(int i=arr.length-1;i>0;i--){
53-
for(int j=0;j<i;j++){
54-
if(arr[j]>arr[j+1]){
55-
swap(arr,j,j+1);
56-
}
57-
}
58-
}
59-
}
60-
61-
private void swap(int[] arr,int i,int j){
62-
int tmp = arr[i];
63-
arr[i] = arr[j];
64-
arr[j] = tmp;
65-
}
66-
```
67-
68-
添加一个状态变量对冒泡排序进行优化:
69-
70-
```java
71-
public void sort(int[] arr){
72-
boolean isSorted = false;
73-
for(int i=arr.length-1;i>0 && !isSorted ;i--){
74-
//!isSorted 条件,当 isSorted=true 时说明是有序的,则不需要再执行了
75-
isSorted = true; //初始时认为是有序的
76-
for(int j=0;j<i;j++){
77-
if(arr[j]>arr[j+1]){
78-
isSorted = false; //存在逆序,则是无序的
79-
swap(arr,j,j+1);
80-
}
81-
}
82-
}
83-
}
84-
85-
private void swap(int[] arr,int i,int j){
86-
int tmp = arr[i];
87-
arr[i] = arr[j];
88-
arr[j] = tmp;
89-
}
90-
```
91-
92-
冒泡排序是稳定的排序算法,平均时间复杂度是 O(n^2)。
93-
94-
95-
96-
### III、插入排序
97-
98-
插入排序思路:**每次都将当前元素插入到左侧已经排序的数组中,使得插入之后左侧数组依然有序**
99-
100-
对于数组 {3, 5, 2, 4, 1}, 它具有以下逆序:(3, 2), (3, 1), (5, 2), (5, 4), (5, 1), (2, 1), (4, 1), 插入排序每次只能交换相邻元素,令逆序数量减少 1,因此插入排序需要交换的次数为逆序数量。
101-
102-
插入排序的复杂度取决于数组的初始顺序,如果数组已经部分有序了,逆序较少,那么插入排序会很快。
103-
104-
```java
105-
public void sort(int[] arr){
106-
for(int i=1;i<arr.length;i++){ //默认 arr[0] 已经有序
107-
//a[0,,,i] 已经有序,加入一个元素后,进行一次冒泡(从后向前的),就必然是有序的了。
108-
for(int j=i;j>0 && arr[j-1]>arr[j];j--){
109-
swap(arr,j-1,j);
110-
}
111-
}
112-
}
113-
114-
private void swap(int[] arr,int i,int j){
115-
int tmp = arr[i];
116-
arr[i] = arr[j];
117-
arr[j] = tmp;
118-
}
119-
```
120-
121-
122-
123-
### IV、希尔排序
124-
125-
对于大规模的数组,插入排序很慢,因为它只能交换相邻的元素,每次只能将逆序数量减少 1。
126-
127-
希尔排序的出现就是为了解决插入排序的这种局限性,它通过交换不相邻的元素,每次可以将逆序数量减少大于 1。
128-
129-
希尔排序思路:**希尔排序使用插入排序对间隔 h 的序列进行排序。通过不断减小 h,最后令 h=1,就可以使得整个数组是有序的。**
130-
131-
```java
132-
public void sort(int[] arr){
133-
int N = arr.length;
134-
135-
int h=1;
136-
while(h<N/3){
137-
h = 3*h+1;
138-
}
139-
140-
while(h>=1){
141-
for(int i=1;i<N;i++){
142-
for(int j=i;j>=h && arr[j-h]>arr[j];j-=h){
143-
swap(arr,j-h,j);
144-
}
145-
}
146-
h /=3;
147-
}
148-
//注意 h 的值最终会减为 1
149-
}
150-
151-
private void swap(int[] arr,int i,int j){
152-
int tmp = arr[i];
153-
arr[i] = arr[j];
154-
arr[j] = tmp;
155-
}
156-
```
157-
158-
159-
160-
## 2、常见的高级排序算法
161-
162-
### I、归并排序
163-
164-
归并排序的思想:**将数组分成两部分,分别进行排序,然后归并起来**
165-
166-
```java
167-
public void sort(int[] arr){
168-
sort(arr,0,arr.length-1);
169-
}
170-
171-
private void sort(int[] arr,int l,int r){
172-
if(l>=r){
173-
return;
174-
}
175-
int mid = (r-l)/2+l;
176-
//对 [l,mid] 进行排序
177-
sort(arr,l,mid);
178-
//对 [mid+1,r] 进行排序
179-
sort(arr,mid+1,r);
180-
//合并这两个有序数组
181-
merge(arr,l,mid,r);
182-
}
183-
184-
//合并两个有序数组
185-
//[l,mid]
186-
//[mid+1,r]
187-
private void merge(int[] arr,int l,int mid,int r){
188-
int[] nums = new int[r-l+1];
189-
int index=0;
190-
int i=l;
191-
int j=mid+1;
192-
while(i<=mid && j<=r){
193-
if(arr[i]<arr[j]){
194-
nums[index++] = arr[i++];
195-
}else{
196-
nums[index++] = arr[j++];
197-
}
198-
}
199-
while(i<=mid){
200-
nums[index++]=arr[i++];
201-
}
202-
while(j<=r){
203-
nums[index++]=arr[j++];
204-
}
205-
206-
index=0;
207-
for(int k=l;k<=r;k++){
208-
arr[k] = nums[index++];
209-
}
210-
}
211-
```
212-
213-
214-
215-
### II、快速排序
216-
217-
快速排序思路:**快速排序通过一个切分元素将数组分为两个子数组,左子数组小于等于切分元素,右子数组大于等于切分元素,将这两个子数组排序也就将整个数组排序了。**
218-
219-
```java
220-
public void sort(int[] arr){
221-
sort(arr,0,arr.length-1);
222-
}
223-
224-
private void sort(int[] arr,int l,int r){
225-
if(l>=r){
226-
return;
227-
}
228-
int p = partition(arr,l,r);
229-
sort(arr,l,p-1);
230-
sort(arr,p+1,r);
231-
}
232-
233-
private int partition(int[] arr,int l,int r){
234-
int pivot = arr[l];
235-
while(l<r){
236-
while(l<r && arr[r]>=pivot){
237-
r--;
238-
}
239-
arr[l] = arr[r];
240-
while(l<r && arr[l]<=pivot){
241-
l++;
242-
}
243-
arr[r] = arr[l];
244-
}
245-
arr[l]=pivot;
246-
return l;
247-
}
248-
```
249-
250-
251-
252-
## 3、堆
253-
254-
使用数组存储二叉堆,下标从0开始:
255-
256-
<div align="center"><img src="https://gitee.com/duhouan/ImagePro/raw/master/java-notes/dataStructure/heap//heap_4.png" width="600px"/></div>
257-
258-
259-
260-
261-
262-
## 4、堆排序
263-
264-
堆排序思路:**把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列,从正向来看就是一个递增序列,这就是堆排序。**
265-
266-
```java
267-
public void sort(int[] arr){
268-
int N = arr.length;
269-
//构建大根堆
270-
for(int i=parent(N-1);i>=0;i--){ //从中间位置开始下沉
271-
sink(arr,i,N);
272-
}
273-
274-
//大根堆的根节点就是最大值
275-
while(N>0){
276-
swap(arr,0,N-1);
277-
//每次交换堆的最后一个元素和堆的一个元素,每次获取的最大值
278-
N--; // 相当于删除堆的第一个元素
279-
sink(arr,0,N); //每次再从 0 位置开始下沉,维护长度为 (N-1) 的大根堆
280-
}
281-
}
282-
283-
// 从 i 位置开始下沉
284-
// N 是堆中元素,可以动态变化
285-
private void sink(int[] num,int i,int N){
286-
while (leftChild(i)<N){
287-
int j = leftChild(i);
288-
if(j+1<N && num[j]<num[j+1]){
289-
j = rightChild(i);
290-
}
291-
if(num[i]>=num[j]){
292-
break;
293-
}
294-
swap(num,i,j); // i 位置和 j 位置元素交换
295-
i=j; // i 元素已经下沉,需要继续下沉
296-
}
297-
}
298-
299-
private int parent(int i){
300-
if(i==0){
301-
throw new IllegalArgumentException("index=0 时,不存在父节点");
302-
}
303-
return (i-1)/2;
304-
}
305-
306-
private int leftChild(int i){
307-
return 2*i+1;
308-
}
309-
310-
private int rightChild(int i){
311-
return 2*i+2;
312-
}
313-
314-
private void swap(int[] arr,int i,int j){
315-
int tmp = arr[i];
316-
arr[i] = arr[j];
317-
arr[j] = tmp;
318-
}
319-
```
320-
321-
322-
323-
## 5、数组中的逆序对
3+
## 1、数组中的逆序对
3244

3255
[数组中的逆序对](https://www.nowcoder.com/practice/96bd6684e04a44eb80e6a68efc0ec6c5?tpId=13&tqId=11188&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
3266

@@ -378,7 +58,7 @@ private void merge(int[] arr,int l,int m,int r){
37858

37959

38060

381-
## 6、最小的 k 个数(44)
61+
## 2、最小的 k 个数
38262

38363
[最小的 k 个数](https://www.nowcoder.com/practice/6a296eb82cf844ca8539b57c23e6e9bf?tpId=13&tqId=11182&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
38464

@@ -437,7 +117,7 @@ private int partion(int[] nums,int start,int end){
437117

438118

439119

440-
## 7、颜色分类
120+
## 3、颜色分类
441121

442122
[颜色分类](https://leetcode-cn.com/problems/sort-colors/)
443123

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,47 @@
1-
# 位图
1+
# 位图
2+
3+
## 位图(Bit-map)
4+
5+
位图(Bit-map)就是用一个 bit 位来标记某个元素对应的 value, 而 key 即是该元素。由于采 用了 bit 为单位来存储数据,因此在存储空间方面,可以大大节省。
6+
7+
位图通过使用 bit 数组来表示某些元素是否存在,可进行数据的快速查找、判重、删除,一般来说数 据范围是 int 的 10 倍以下。
8+
9+
例如要对 {4,7,2,5,3} 进行排序,可以设置一个范围为 0~8 的比特数组,读入数据之后将比特数组第 2、3、4、5、7 位置设置为 1。最后从头遍历比特数组,将比特数组值为 1 的数据读出得到 {2,3,4,5,7} 这个已排序的数据。
10+
11+
## 1. 2.5 亿个数的去重
12+
13+
### 问题
14+
15+
在 2.5 亿个整数中找出**不重复的整数**,注,内存不足以容纳这 2.5 亿个整数。
16+
17+
### 分析
18+
19+
采用 2-Bitmap(即每个整数分配 2 bit:00 表示不存在,01表示出现一次,10 表示多次,11 表示无意义)。
20+
21+
这样,所占内存 2^32 * 2 bit = 1 GB。
22+
23+
### 步骤
24+
25+
- 扫描。
26+
27+
扫描这 2.5 亿个整数,查看 Bitmap 中相对应位, 如果是 00 变 01,01 变 10,10 保持不变。
28+
29+
- 输出。
30+
31+
查看对应位为 01 的整数,输出即可。
32+
33+
34+
35+
## 2. 整数的快速查询
36+
37+
### 问题
38+
39+
给 40 亿个不重复的 unsigned int 的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那 40 亿个数当中?
40+
41+
### 分析
42+
43+
使用用位图的方法,40*10^8 = 4 *10^9 bit = 500 M,所以可以申请 512M 的内存,一个 bit 位代表一个 unsignedint 值。
44+
45+
### 步骤
46+
47+
读入 40 亿个数,设置相应的 bit 位,读入要查询的数,查看相应 bit 位是否为 1,为 1 表示存在,为 0 表示不存在。

0 commit comments

Comments
 (0)