排序性质须知:
稳定性:
判断某排序算法是否稳定,我们可以简单理解成:
排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同如果相同,则是稳定的排序方法。
如果不相同,则是不稳定的排序方法
稳定排序的好处:
如果我们只对一串数字排序,那么稳定与否确实不重要,因为一串数字的属性是单一的,就是数字值的大小。但是排序的元素往往不只有一个属性,例如我们对一群人按年龄排序,但是人除了年龄属性还有身高体重属性,在年龄相同时如果不想破坏原先身高体重的次序,就必须用稳定排序算法。
也就是说:如果你想按照某一属性排序,但是不想打破排序之前,这个属性值相同的元素的原有位置。
图片来源于网络,侵权必删!
.
1.冒泡排序
性质:
稳定,时间复杂度为O(n^2),属于交换排序
思路:
最简单、最好理解、最容易上手的方法,那肯定也就是最low的方法了,嘿嘿嘿
控制一个数,让他和他后面的所有的数逐一比较,从而找到最小的(或者最大的),然后再将其赋值给这个数,完事后,再控制这个数的下一个数……
就这么简单!!!
/**
* 冒泡排序法
* @param num
*/
public static void maoPaoSort(int[] num) {
int len = num.length;
for(int i = 0; i < len - 1; i++)
for(int j = i + 1; j < len; j++) {
if(num[i] <= num[j])
continue;
int temp = num[i];
num[i] = num[j];
num[j] = temp;
}
}
2.快速排序
性质:
不稳定,时间复杂度为O(logN),属于交换排序
思路:
对冒泡排序的一中改进,基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
也就是说:在数组中任意找到一个位置,位置左边的数都不大于该位置的数,位置右边的数都不小于该位置。
一趟算法步骤:
1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;
2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];
3)从j开始向前搜索,即由后开始向前搜索(j–),找到第一个小于key的值A[j],将A[j]和A[i]的值交换;
4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]的值交换;
5)重复第3、4步,直到i=j;
/**
* 快速排序
* @param num
*/
public static void sort(int[] num) {
kuaiSuSort(num, 0, num.length - 1);
}
public static void kuaiSuSort(int[] num, int left, int right) {
int l = left;
int r = right;
int key = num[l];
while(l < r) {
while(l < r && key < num[r])
r--;
while(l < r && key > num[l])
l++;
if(l < r && num[l] == num[r]) { //这里让l < r 还是很关键的
l++;
}else {
int temp = num[l];
num[l] = num[r];
num[r] = temp;
}
}
//下面两个if的检测,就是递归终止的条件
if(l - 1 > left)
kuaiSuSort(num, left, l - 1);
if(r + 1 < right)
kuaiSuSort(num, r+1, right);
}
3.选择排序
性质:
不稳定,时间复杂度O()
思路:
选择排序,听名字也就能猜出个大概,选择最特别的值,放在最特别的位置!
实现原理是:
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在待排序数据中的的起始(末尾)位置,直到全部待排序的数据元素排完。
/**
* 选择排序
* @param num
*/
public static void xuanZeSort(int[] num) {
int len = num.length;
for(int i = 0; i < len - 1; i++) {
int min = num[i];
for(int j = i + 1; j < len; j++)
if(min > num[j]) {
int temp = num[j];
num[j] = min;
min = temp;
}
num[i] = min;
}
}
4.堆排序(优先队列)
说明:
堆排序不是我们所认知的普通排序,他不能让一组无序的数字变得有序,只能找出最大的值,或者最小的值。
优先队列的实现其实就是对排序,在做树高平衡时会用到。最近用的C多,以下是用C实现的最小堆:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PRIORITY_QUEUE_INIT_SIZE 10
typedef struct{
int value;
int priority;
}Type;
typedef struct{
int capacity;
int size;
Type *array;
}PriorityQueue;
PriorityQueue *init_queue(){
PriorityQueue *pq = (PriorityQueue *)malloc(sizeof(PriorityQueue));
if(pq == NULL){
printf("PriorityQueue malloc failed!\n");
return 0;
}
pq->size = 0;
pq->capacity = PRIORITY_QUEUE_INIT_SIZE;
pq->array = (Type*)malloc(pq->capacity * sizeof(Type));
if(pq->array == NULL){
printf("array malloc failed!\n");
free(pq);
return 0;
}
memset(pq->array, 0, PRIORITY_QUEUE_INIT_SIZE);
return pq;
}
int is_empty(PriorityQueue *pq){
if(pq == NULL)
return 0;
return pq->size == 0 ? 1 : 0;
}
int is_full(PriorityQueue *pq){
if(pq != NULL && pq->size == pq->capacity)
return 1;
return 0;
}
int comp(Type *val1, Type *val2){
if(val1->priority > val2->priority)
return 1;
else if(val1->priority == val2->priority)
return 0;
else
return -1;
}
int assign(Type *left, Type *right){
left->value = right->value;
left->priority = right->priority;
}
int en_queue(PriorityQueue *pq, Type *val){
if(pq == NULL || val == NULL){
printf("pointer is null in en_queue!\n");
return 0;
}
if(is_full(pq)){
printf("the queue is full!\n");
return 0;
}
int i = pq->size;
for(; i > 0 && comp(&pq->array[(i - 1)/2], val) == 1; i = (i - 1)/2)
assign(&pq->array[i], &pq->array[(i - 1)/2]);
pq->size++;
assign(&pq->array[i], val);
return 1;
}
int de_queue(PriorityQueue *pq, Type *min){
if(is_empty(pq))
return 0;
int i, child;
assign(min, &pq->array[0]);
Type last;
assign(&last, &pq->array[--pq->size]);
for(i = 0; i * 2 + 1 < pq->size; i = child){
child = i * 2 + 1;
if(child < pq->size && comp(&pq->array[child], &pq->array[child + 1]) == 1)
child++;
if(comp(&last, &pq->array[child]) == 1)
assign(&pq->array[i], &pq->array[child]);
else
break;
}
assign(&pq->array[i], &last);
}
int front(PriorityQueue *pq, Type *min){
if(pq == NULL){
printf("pointer is null in front!\n");
return 0;
}
assign(min, &pq->array[0]);
return 1;
}
int free_queue(PriorityQueue *pq){
if(pq != NULL){
if(pq->array != NULL)
free(pq->array);
free(pq);
return 1;
}
return 0;
}
int main(){
PriorityQueue *pq = init_queue();
printf("call de_queue:\n");
for(int i = 0; i < PRIORITY_QUEUE_INIT_SIZE; i++){
Type val;
val.value = rand() % 100;
val.priority = rand() % 10;
printf("val.value = %d, val.priority = %d\n", val.value, val.priority);
en_queue(pq, &val);
}
printf("\ncall de_queue:\n");
while(pq->size > 0){
Type min;
de_queue(pq, &min);
printf("min.value = %d, min.priority = %d\n",min.value, min.priority);
}
free_queue(pq);
}
5.插入排序
性质:
稳定,时间复杂度为O(n^2)
思路:
插入排序的基本操作就是:将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序。
一般的步骤:
- 选取数组的第一个元素作为有序数据
- 将数组的第二个元素存到一个中间变量中,用中间变量和第一个元素比较:如果比第一个元素小,就把第一个元素向前移一位,再继续比较,因为前面已经没有了,所以就把中间变量放在第一个元素的位置上;如果中间变量比第一个元素大,就直接跳过,看第三个元素。
- 然后基本上就是重复上面的步骤,会出现两种情况:一种是数组一直到头都没找到比中间变量小的元素,那把中间变量放在首位就ok;另一种是找到了比中间变量小的元素,就把中间变量放在这个元素的后面即可。
采用普通方法插入:
/**
* 直接插入排序
* @param num
*/
public static void chaRuSort(int[] num) {
int temp;
for(int i = 1; i < num.length; i++) {
temp = num[i];
int j = i - 1;
if(temp >= num[j])
continue;
while(j >= 0 && temp < num[j]) {
num[j + 1] = num[j];
j--;
}
num[j + 1] = temp;
}
}
采用二分法插入:
在这里插入代码片
6.希尔排序
性质:
不稳定,复杂度的下界是n*log2n,没有快速排序算法快 O(n(logn)),相比于直接插入法平均复杂度降低。
思路:
希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因D.L.Shell于1959年提出而得名。
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
如下图:
图片来源于网络,侵必删!!!
/**
* 希尔排序
* @param num
*/
public static void shellSort(int[] num) {
for(int gap = num.length; gap > 0; gap /=2) {
for(int i = gap; i < num.length; i++) {
int temp = num[i];
int j = i;
while(j - gap >= 0 && num[j - gap] > temp) {
num[j] = num[j - gap];
j = j - gap;
}
num[j] = temp;
}
}
}
7.归并排序
性质:
稳定,时间复杂度为O(n(logn));
思路:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
- 归并操作的工作原理如下:
第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置
第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3直到某一指针超出序列尾
将另一序列剩下的所有元素直接复制到合并序列尾
这个就和合并两个有序链表一样,其实也很好理解,自己写几个数字,然后跟着代码捋一遍,就明白了。
/**
* 归并排序
* @param num
* @return
*/
public static int[] mergeSort(int[] num) {
if (num != null && num.length != 0)
return mSort(num, 0, num.length - 1);
return null;
}
public static int[] mSort(int[] num, int l, int h) {
if (l == h)
return new int[] { num[l] };
int m = (l + h) / 2;
int[] leftA = mSort(num, l, m);//左边已经排好序的数组
int[] rightA = mSort(num, m + 1, h);//右边已经排好序的数组
int[] newA = new int[leftA.length + rightA.length];//申请一个新数组空间,大小为左边和右边的大小和
int i = 0, k = 0, j = 0;
while (i < leftA.length && j < rightA.length)
newA[k++] = leftA[i] < rightA[j] ? leftA[i++] : rightA[j++];
while (i < leftA.length)
newA[k++] = leftA[i++];
while (j < rightA.length)
newA[k++] = rightA[j++];
return newA;
}