插入排序
冒泡排序
选择排序
快速排序
堆排序
归并排序
希尔排序
记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。
快速排序
两种方法,1.指针两边往中间走。2.指针都在开头 往后走。
1 public static void quickSort(int[] arr, int start, int end) {
2 if (start>=end)
3 return;
4 int key = partionByBoth(arr, start,end);
5 quickSort(arr, start,key-1);
6 quickSort(arr, key+1,end);
7 }
8
9 public static int partionByEither(int[] arr, int start, int end) {
10 int mid = start+((end-start)>>2);
11 int key = arr[mid];
12 swap(arr,mid,end);
13 int p = start;
14 int q = end-1;
15 while (p<=q) {
16 if(arr[p] > key){
17 swap(arr,p,q);
18 q--;
19 }
20 else
21 p++;
22 }
23 swap(arr,p,end);
24 return p;
25 }
26 public static int partionByBoth(int[] arr, int start, int end) {
27 int mid = start+((end-start)>>2);
28 int key = arr[mid];
29 swap(arr,mid,end);
30 int p = start;
31 for (int q = start; q < end; q++) {
32 if (arr[q] <= key) {
33 swap(arr, q, p);
34 p++;
35 }
36 }
37 swap(arr,p,end);
38 return p;
39 }
40
41 private static void swap(int[] arr, int a, int b) {
42 int temp = arr[a];
43 arr[a] = arr[b];
44 arr[b] = temp;
45 }
View Code
快速排序非递归方案
1 public static void quickSort(int[] arr) {
2 if (arr == null)
3 return;
4 int[] stack = new int[32]; //默认递归深度最深为32/2,因为快速排序是二叉树的递归
5 int top=-1; //栈顶指针
6 stack[++top]=0; //初始栈的两个值
7 stack[++top]=arr.length-1;
8 while (top>0) {
9 int end = stack[top--];
10 int start = stack[top--]; //从栈顶取出值
11 int key = arr[end];
12 int p = start;
13 for (int q = start; q < end; q++) { //快速排序的划分
14 if (arr[q] < key) {
15 int temp = arr[p];
16 arr[p] = arr[q];
17 arr[q] = temp;
18 p++;
19 }
20 }
21 int temp = arr[p];
22 arr[p] = arr[end];
23 arr[end] = temp;
24
25 if (p+1 < end) { //压栈 下一半数组的排序
26 stack[++top] = p+1;
27 stack[++top] = end;
28 }
29 if (p-1>start) { //压栈 上一半数组的排序
30 stack[++top] = start;
31 stack[++top] = p-1;
32 }
33 //注意,执行上述的排序 是 反过来执行的,先执行上一半,再执行下一半,这就是栈
34 }
排序算法 :计数排序、桶排序与基数排序
计数排序
/*
算法的步骤如下:
1.找出待排序的数组中最大和最小的元素
2.统计数组中每个值为i的元素出现的次数,存入数组C的第i项
3.对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
4.反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
*/
void counting_sort(int *ini_arr, int *sorted_arr, int n)
{
int *count_arr = (int *)malloc(sizeof(int) * NUM_RANGE);
int i, j, k;
//统计数组中,每个元素出现的次数
for(k=0; k<NUM_RANGE; k++){
count_arr[k] = 0;
}
for(i=0; i<n; i++){
count_arr[ini_arr[i]]++;
}
//使count_arr数组中的值表示此下标的位置
//就是使之 下标-值 -》 值-下标
//eg: 1,3,4 ->01011->01123
//所以1的值下标在1上,3的值下标在2上,4的值下标在3上
for(k=1; k<NUM_RANGE; k++){
count_arr[k] += count_arr[k-1];
}
for(j=n-1 ; j>=0; j--){
int elem = ini_arr[j];
int index = count_arr[elem]-1;
sorted_arr[index] = elem;
count_arr[elem]--;
}
free(count_arr);
}
桶排序
划分成M个的子区间(桶) 。然后基于某种映射函数
假如待排序列K= {49、 38 、 35、 97 、 76、 73 、 27、 49 }。这些数据全部在1—100之间。因此我们定制10个桶,然后确定映射函数f(k)=k/10。则第一个关键字49将定位到第4个桶中(49/10=4)。依次将所有关键字全部堆入桶中,并在每个非空的桶中进行快速排序。
O(N+C),其中C=N*(logN-logM)。如果相对于同样的N,桶数量M越大,其效率越高,最好的时间复杂度达到O(N)。 当然桶排序的空间复杂度 为O(N+M)
基数排序
每个位上的数字为一个关键字。
基数排序的思想就是将待排数据中的每组关键字依次进行桶分配。
278、109、063、930、589、184、505、269、008、083
我们将每个数值的个位,十位,百位分成三个关键字: 278 -> k1(个位)=8 ,k2(十位)=7 ,k3=(百位)=2。
对所有数据的k1关键字进行桶分配(因为,每个数字都是 0-9的,因此桶大小为10),再依次输出桶中的数据得到下面的序列。
930、063、083、184、505、278、008、109、589、269
再对上面的序列接着进行针对k2的桶分配,输出序列为:
505、008、109、930、063、269、278、083、184、589
最后针对k3的桶分配,输出序列为:
008、063、083、109、184、269、278、505、589、930
基数排序的时间复杂度将是O(d*2N) ,当然d要远远小于N,因此基本上还是线性级别的。基数排序的空间复杂度为O(N+M),其中M为桶的数量。一般来说N>>M,因此额外空间需要大概N个左右。
基数排序的应用:线性时间的原地置换(空间O1)排序
1 /**
2 * 题目:一个大小为N的数组,里面是N个整数,怎样去除重复,要求时间复杂度为O(n),空间复杂度为O(1).
3 * 实际需求就是排序,线性时间的原地置换排序
4 * 下列算法时间复杂度为 O(n) -> 上界32*n
5 * @author hawksoft
6 *
7 */
8 public class DeleteRepeatedInt {
9 //===================================================
10 //排序算法修正部分
11 /// <summary>
12 /// 需要除掉重复的整数的数组,注意这里我没有处理负数情况,
13 /// 其实负数情况只要先用0快排分一下组,然后各自用以下算法进行处理即可。
14 /// 另外因为是整数,这里没考虑32位符号位,只考虑31位。
15 /// 题目分析:从要求来看,如果一个数组是排好序的,除掉重复就很简单,因此就转换成了
16 /// 排序算法寻找,这种算法需要满足:线性时间,常量内存,原地置换。但纵观这么多算法,比较排序肯定不行,
17 /// 那么就只有基数排序,桶排序和计数排序,但基数排序依赖于位排序,而且要求位排序是稳定的,
18 /// 且不能过多使用辅助空间,计数排序排除,因为计数排序无法原地置换,桶排序也需要辅助空间,所以最后考虑用
19 /// 基数排序。但问题是如何选择位排序,因为位上只有0和1,因此有其特殊性,使用快排的分组就可以达到线性,
20 /// 但问题是这种算法虽然是线性,原地置换,但不稳定。所以要利用一种机制来确保快排是稳定的。经过一段时间思考,
21 /// 发现,如果从高位开始排序,假设前K位是排好的,对K+1进行排序时,只针对前K位的相同的进行,前K位不相同,也不可
22 /// 能相等,第K+1位也不影响结果,而前K位相同的排序,就不怕快排的不稳定了,因为这个不稳定不会影响到最终结果。
23 /// 下面就是算法:
24 /// </summary>
25 /// <param name="A"></param>
26 private static void BitSortAndDelRepeatorsB(int[] A)
27 {
28 //获取数组长度
29 int theN = A.length;
30 //从高位到低位开始排序,这里从31位开始,32位是符号位不考虑,或者单独考虑。
31 for (int i = 31; i >= 1; i--)
32 {
33 //当前排序之前的值,只有该值相同才进行快排分组,如果不相同,则重新开始另外一次快排
34 //这很关键,否则快排的不稳定就会影响最后结果.
35 int thePrvCB = A[0] >> (i) ;
36 //快排开始位置,会变化
37 int theS = 0;
38 //快排插入点
39 int theI = theS-1;
40 //2进制基数,用于测试某一位是否为0
41 int theBase = 1 << (i-1);
42 //位基元始终为0,
43 int theAxBit = 0;
44
45 //分段快排,但总体上时间复杂度与快排分组一样.
46 for (int j = 0; j < theN; j++)
47 {
48 //获取当前数组值的前面已拍过序的位数值。
49 int theTmpPrvCB = A[j] >> (i);
50 //如果前面已排过的位不相同,则重新开始一次快排.
51 if (theTmpPrvCB != thePrvCB)
52 {
53 theS = j;
54 theI = theS - 1;
55 theAxBit = 0;
56 thePrvCB = theTmpPrvCB;
57 j--;//重新开始排,回朔一位.
58 continue;
59 }
60 //如果前面的数相同,则寻找第1个1,thI指向其
61 //如果相同,则按快排处理
62 int theAJ = (A[j] & (theBase)) > 0 ? 1 : 0; ;//(A[j] & (theBase)) > 0 ? 1 : 0;(A[j] >> (i - 1)) & 1
63 //如果是重新开始排,则寻找第1个1,并人theI指向其.这可以减少交换,加快速度.
64 if (theI < theS)
65 {
66 if (theAJ == 0)
67 {
68 continue;
69 }
70 theI = j;//Continue保证J从theI+1开始.
71 continue;
72 }
73 //交换.
74 if (theAJ <= theAxBit)
75 {
76 int theTmp = A[j];
77 A[j] = A[theI];
78 A[theI] = theTmp;
79 theI++;
80 }
81 }
82 }
83 }
84 public static void main(String[] args) {
85 int[] A = {1,2,3,4,6,8,4,3,1};
86 BitSortAndDelRepeatorsB(A);
87 for (int i : A) {
88 System.out.println(i);
89 }
90 }
91 }
View Code
下限nlog(n)),
那么就只有基数排序,桶排序和计数排序,但基数排序依赖于位排序,而且要求位排序是稳定的,
且不能过多使用辅助空间,计数排序排除,因为计数排序无法原地置换,桶排序也需要辅助空间,所以最后考虑用基数排序。
快排的分组就可以达到线性.
利用一种机制来确保快排是稳定的。经过一段时间思考,发现,如果从高位开始排序,假设前K位是排好的,对K+1进行排序时,只针对前K位的相同的进行,前K位不相同,也不可能相等,第K+1位也不影响结果,而前K位相同的排序,就不怕快排的不稳定了,因为这个不稳定不会影响到最终结果。
假设前K位排好序了,对前k位快排分组,当排到第k+1时,重新设置插入点,起始点,重新快排分组。
eg:初始数据: 6 0 7 0 也就是 110 000 111 000
排序进行到 000 000 111 110
后一步 排完第一个第二个时,轮到第三个,发现111前面的两个和第一个不一致,重新初始化。针对 111 和 110 快排分组。
结果 000 000 110 111
AVL树和红黑树