常见的七大经典排序
引言排序稳定性划分①直接插入排序代码如下
②希尔排序代码如下
③选择排序代码如下
④堆排序代码如下
⑤冒泡排序代码如下
⑥快速排序三种改进版本三种快排代码如下
⑦归并排序代码如下
引言
作为数据结构又一大经典必考面试题,手撕排序算法题是很多人的噩梦,今天我们来梳理探索一下这七大经典算法。明其理,熟其用,穷其变。
排序
排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。分内部排序和外部排序,若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序。反之,若参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序。内部排序的过程是一个逐步扩大记录的有序序列长度的过程。(来自百度百科)
稳定性划分
所谓稳定性指的是两个想等元素,在排序过程发生之后,两者的先后位置没有发生改变.
①直接插入排序
思想:每次从无序表中取出第一个元素,把它插入到有序表的合适位置,使有序表仍然有序。 第一趟比较前两个数,然后把第二个数按大小插入到有序表中; 第二趟把第三个数据与前两个数从后向前扫描,把第三个数按大小插入到有序表中;依次进行下去,进行了(n-1)趟扫描以后就完成了整个排序过程。 直接插入排序是由两层嵌套循环组成的。外层循环标识并决定待比较的数值。内层循环为待比较数值确定其最终位置。直接插入排序是将待比较的数值与它的前一个数值进行比较,所以外层循环是从第二个数值开始的。当前一数值比待比较数值大的情况下继续循环比较,直到找到比待比较数值小的并将待比较数值置入其后一位置,结束该次循环。
1.元素集合越接近有序,直接插入排序算法的时间效率越高 2. 时间复杂度:O(N^2) 3. 空间复杂度:O(1),它是一种稳定的排序算法 4. 稳定性:稳定
代码如下
void InsertSort(int* a
, int n
)
{
for (size_t i
= 0; i
< n
- 1; ++i
)
{
int end
= i
;
int tmp
= a
[end
+ 1];
while (end
>= 0)
{
if (tmp
< a
[end
])
{
a
[end
+1]=a
[end
];
--end
;
}
else
{
break;
}
}
a
[end
+ 1] = tmp
;
}
}
②希尔排序
思想:希尔排序是直接插入排序的优化版本,先选定一个整数,把待排序数组中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。
1.时间复杂度:O(N1.3—N2) 2.稳定性:不稳定
代码如下
void ShellSort(int* a
, int n
)
{
int gap
= n
-1;
while (gap
> 1)
{
gap
= gap
/ 3 + 1;
for (size_t i
= 0; i
< n
- gap
; ++i
)
{
int end
= i
;
int tmp
= a
[end
+ gap
];
while (end
>= 0)
{
if (tmp
< a
[end
])
{
a
[end
+ gap
] = a
[end
] ;
end
-= gap
;
}
else
{
break;
}
}
a
[end
+ gap
] = tmp
;
}
}
}
③选择排序
思想:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。
1.直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用 2. 时间复杂度:O(N^2) 3. 空间复杂度:O(1) 4. 稳定性:不稳定
代码如下
void AdjustDown(int *a
, int size
, int root
)
{
int parent
= root
;
int chrild
= parent
* 2 + 1;
while (chrild
< size
)
{
if (chrild
+ 1 < size
&& a
[chrild
] < a
[chrild
+ 1])
{
++chrild
;
}
if (a
[chrild
] > a
[parent
])
{
Swap(&a
[parent
], &a
[chrild
]);
parent
= chrild
;
chrild
= parent
* 2 + 1;
}
else
{
break;
}
}
}
void HeapSort(int *a
, int n
)
{
for (int i
= (n
- 1 - 1) / 2; i
>= 0;--i
)
{
AdjustDown(a
, n
, i
);
}
int end
= n
- 1;
while (end
> 0)
{
Swap(&a
[0], &a
[end
]);
AdjustDown(a
, end
, 0);
--end
;
}
}
④堆排序
思想:堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。在堆的数据结构中,堆中的最大值总是位于根节点(在优先队列中使用堆的话堆中的最小值位于根节点)。此处需要用到向下调整算法,需要注意的是排升序要建大堆,排降序建小堆。
1.堆排序使用堆来选数,效率就高了很多。 2. 时间复杂度:O(N*logN) 3. 空间复杂度:O(1) 4. 稳定性:不稳定
代码如下
void AdjustDown(int* a
, int size
,int root
)
{
int parent
= root
;
int child
= parent
* 2 + 1;
while (child
< size
)
{
if (child
+ 1 <size
&& a
[child
+ 1] < a
[child
])
{
++child
;
}
if (a
[child
] < a
[parent
])
{
Swap(&a
[child
], &a
[parent
]);
parent
= child
;
child
= parent
* 2 + 1;
}
else
{
break;
}
}
}
void HeapSort(int* a
, int n
)
{
for (int i
= (n
- 1 - 1) / 2; i
>= 0; --i
)
{
AdjustDown(a
, n
, i
);
}
int end
= n
- 1;
while (end
> 0)
{
Swap(&a
[0],&a
[end
]);
AdjustDown(a
, end
, 0);
end
--;
}
}
⑤
⑤冒泡排序
思想:冒泡排序是一种较为简单的排序,因为他的简单粗暴导致它的算法特性差。以升序为例,就是重复遍历数据,比较前后两个元素的大小,把两者大的一个交换到后面,直到把最大的元素调到最后边,最小的调到最前边为止。
时间复杂度:T(n)=O(n²) 空间复杂度:S(n)=O(1) 稳定性: 稳定排序
代码如下
void BubbleSort(int* a
, int n
)
{
int end
= n
- 1;
while (end
>=0)
{
int flag
= 0;
for (int i
= 0; i
< end
; ++i
)
{
if (a
[i
]>a
[i
+ 1])
{
flag
= 1;
Swap(&a
[i
], &a
[i
+ 1]);
}
}
if (flag
== 0)
{
break;
}
--end
;
}
}
⑥快速排序
思想:(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。 (2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。 (3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。 (4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
1 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序 2. 时间复杂度:O(N*logN) 3. 空间复杂度:O(logN) 4. 稳定性:不稳定
三种改进版本
hoare版本 挖坑法 前后指针版本
三种快排代码如下
int GetMid(int* a
, int left
, int right
)
{
assert(a
);
int mid
= left
+ (right
- left
) / 2;
if (a
[left
] < a
[mid
])
{
if (a
[mid
] < a
[right
])
{
return mid
;
}
else if (a
[left
]>a
[right
])
{
return left
;
}
else
{
return right
;
}
}
else
{
if (a
[mid
] > a
[right
])
{
return mid
;
}
else if (a
[left
]<a
[right
])
{
return left
;
}
else
{
return right
;
}
}
}
int PartSort1(int* a
, int left
,int right
)
{
int mid
= GetMid(a
, left
, right
);
Swap(&a
[mid
], &a
[right
]);
int key
=right
;
while (left
< right
)
{
while (left
< right
&& a
[left
] <= a
[key
])
++left
;
while (left
< right
&& a
[right
]>=a
[key
])
--right
;
Swap(&a
[left
], &a
[right
]);
}
Swap(&a
[key
], &a
[left
]);
return left
;
}
void QuickSort1(int* a
, int left
, int right
)
{
if (right
<= left
)
return;
int index
= PartSort1(a
, left
, right
);
QuickSort1(a
, left
, index
- 1);
QuickSort1(a
, index
+ 1, right
);
}
int PartSort2(int* a
, int left
, int right
)
{
int key
= a
[right
];
while (left
< right
)
{
while (left
< right
&& a
[left
] <= key
)
++left
;
a
[right
] = a
[left
];
while(left
< right
&& a
[right
] >= key
)
--right
;
a
[left
] = a
[right
];
}
a
[left
] = key
;
return left
;
}
void QuickSort2(int* a
, int left
, int right
)
{
if (right
<= left
)
return;
int index
= PartSort2(a
, left
, right
);
QuickSort2(a
, left
, index
- 1);
QuickSort2(a
, index
+ 1, right
);
}
int PartSort3(int* a
, int left
, int right
)
{
int cur
= left
;
int prev
= left
- 1;
int key
= a
[right
];
while (cur
< right
)
{
if (a
[cur
] < key
&& ++prev
!= cur
)
{
Swap(&a
[prev
], &a
[cur
]);
}
++cur
;
}
++prev
;
Swap(&a
[prev
],&a
[right
]);
return prev
;
}
void QuickSort3(int* a
, int left
, int right
)
{
if (right
<= left
)
return;
int index
= PartSort3(a
, left
, right
);
QuickSort3(a
, left
, index
- 1);
QuickSort3(a
, index
+ 1, right
);
}
⑦归并排序
思想:归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
1 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。 2. 时间复杂度:O(N*logN) 3. 空间复杂度:O(N) 4. 稳定性:稳定
代码如下
void _MergeSort(int* a
, int left
, int right
, int* tmp
)
{
if (left
== right
)
return;
int mid
= left
+ (right
- left
) / 2;
_MergeSort(a
, left
, mid
, tmp
);
_MergeSort(a
, mid
+ 1, right
, tmp
);
int begin1
= left
, end1
= mid
;
int begin2
= mid
+ 1, end2
= right
;
int i
= left
;
while (begin1
<= end1
&& begin2
<= end2
)
{
if (a
[begin1
] < a
[begin2
])
{
tmp
[i
++] = a
[begin1
];
++begin1
;
}
else
{
tmp
[i
++] = a
[begin2
];
++begin2
;
}
}
while (begin1
<= end1
)
{
tmp
[i
++] = a
[begin1
];
++begin1
;
}
while (begin2
<= end2
)
{
tmp
[i
++] = a
[begin2
];
++begin2
;
}
memcpy(a
+ left
, tmp
+ left
, sizeof(int)*(i
- left
));
}
void MergeSort(int* a
, int n
)
{
int* tmp
= (int*)malloc(sizeof(int)* n
);
_MergeSort(a
, 0, n
- 1, tmp
);
free(tmp
);
}