第二章是基础排序算法,作为总结回顾练习一发。两年前学习数据结构课的时候学习过,但当时并没有自己实现这些算法,所以印象不是很深刻,
但这些简单的算法笔试面试时也会遇到。PS: 发现大学就应该早些时候参加ACM玩玩,这样以后面试啥的也有套路了。
2.2 冒泡排序
垃圾谭浩强c语言的书上第一次看到的算法。思路就是交换,每次排序把最大的换到底部,或者最小的换到顶部即可。
牛客AC代码:
class BubbleSort {
public:
int* bubbleSort(int* A, int n) {
// write code here
int i=0;int j=0;
for(;i<n;i++){
for(j=i+1;j<n;j++){
if(A[i]>A[j])
swap(A[i],A[j]);
}
}
return A;
}
};
2.3 选择排序
思路每一次选择最大或者最小的元素放在后面或者前面。
class SelectionSort {
public:
int* selectionSort(int* A, int n) {
// write code here
int i = n-1;
int max_index=0;
for(;i>=0;i--){
for(int j=0;j<=i;j++){
if(A[j]>=A[max_index]){
max_index = j;
}
}
if(i!=max_index)
swap(A[i],A[max_index]);
max_index=0;
}
return A;
}
};
2.4 插入排序
思路:每一次把较小的数插入到队伍的前面,或者把较大的数插入到队伍的后面。
class InsertionSort {
public:
int* insertionSort(int* A, int n) {
// write code here
int i=0;
int j=0;
for(;i+1 <= n-1;i++)
if(A[i]<A[i+1])
continue;
else{
int temp = A[i+1];
for(j=i+1;A[j-1]>temp;j--)
A[j]=A[j-1];
A[j] = temp;
}
return A;
}
};
2.5 归并排序
归并排序是分治法的应用典范,思路为把数组进行分组,再合并,先划分为子问题,然后在合并的同时进行排序,明显的一次递归的操作。
class MergeSort {
public:
int* mergeSort(int* A, int n) {
// write code here4
merge_sort(A,0,n-1,n);
return A;
}
void merge_sort(int *A,int left,int right,int n){
if(left<right){
int mid = (left+right)/2;
merge_sort(A,left,mid,n);
merge_sort(A,mid+1,right,n);
merge(A,left,mid,right,n);
}
}
void merge(int *A,int l,int m,int r,int len){ //merge operation
int tmp[len];
int index =0;
int i =l; int j = m+1;
while(i<=m&&j<=r){
if(A[i]<=A[j]){
tmp[index++] = A[i++];
}
else{
tmp[index++] = A[j++];
}
}
while(i<=m) tmp[index++] = A[i++];
while(j<=r) tmp[index++] = A[j++];
for(int i=0;i<index;++i){
A[i+l] = tmp[i];
}
}
};
2.6 快速排序(qsort函数)
快速排序的核心是 Partition 函数的编写与应用。
Partiton 的思路是选择一个数a,一次partition的过程让比a小的数放在a的左边,让比a大的数放在a的右边。
注意应用,partition函数可以用在很多场景中。
这里的partition 函数用的最左边的数和右边的数互相交换。
牛客网AC代码:
class QuickSort {
public:
int* quickSort(int* A, int n) {
// write code here
qsort(A,0,n-1);
return A;
}
void qsort(int* A,int left,int right){
if(left<right){
int index = Partition(A,left,right); // partition 的位置是有序的
qsort(A,left,index-1);
qsort(A,index+1,right);
}
}
int Partition(int A[],int left,int right){
int temp = A[left];
while(left<right){
while(left<right && A[right]>temp) right--;
A[left] = A[right];
while(left<right && A[left]<=temp) left++; //必须考虑数值大小相同的情况
A[right] = A[left];
}
A[left]= temp;
return left;
}
};
2.7 堆排序
堆排序要用到
堆的数据结构,
可以利用数组的特点快速定位指定索引的元素,即用数组来实现堆的操作。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶。
堆可以被看成是一棵树,结点在堆中的高度可以被定义为从本结点到叶子结点的最长简单下降路径上边的数目;定义堆的高度为树根的高度。我们将看到,堆结构上的一些基本操作的运行时间至多是与树的高度成正比,为O(lgn)。
注意一点:建立一个堆的时候要自底向上的把每一个元素都考虑进来,而在堆排序的过程中,调整大顶堆的时候,只需要一次从根节点向下调整即可。
AC代码
class HeapSort {
public:
int* heapSort(int* A, int n) {
// write code here
heapSortFuc(A,0,n-1);
return A;
}
//core code for adjust
void adjust(int *A,int s,int e){
int temp,j;
temp =A[s];
for(j=2*s;j<=e;j*=2){
if( j<e && A[j] < A[j+1])
j++;
if(temp>=A[j])
break;
A[s] = A[j];
s=j;
}
A[s] = temp;
}
void heapSortFuc(int *A,int start,int end){
//initialize a big heap, and adjust the heap from the bottom up
for(int i = end/2;i>=0;i--){
adjust(A,i,end);
}
for(int i=end;i>=1;i--){
swap(A[0],A[i]);
adjust(A,0,i-1);
}
}
};
2.8 希尔排序
希尔排序的实质就是
分组插入排序
,该方法又称缩小增量排序,该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。
相当于对数组进行多次间隔不同的插入排序,但必须保证最后一次的间隔为1,此时退化为插入排序,因为数组基本有序了,在应用API里面,很多排序算法,比如快速排序时在最后基本有序时或者排序的数不多时是使用插入排序的。
class ShellSort {
public:
int* shellSort(int* A, int n) {
// write code here
if(nullptr == A || n < 0 )
return nullptr;
for(int stepLen = n/2; stepLen >= 1; stepLen /=2)
sort(A,n,stepLen);
return A;
}
private:
void sort(int *A,int n,int stepLen){
for(int i = stepLen; i < n; ++i){
for(int j = i ; j >= stepLen; j-=stepLen){
if(A[j] < A[j-stepLen]){
int tmp = A[j];
A[j] = A[j-stepLen];
A[j-stepLen] = tmp;
}
}
}
}
};
2.10 计数排序
计数排序和基数排序都是基于桶排序的思想,即空间换时间的套路。
计数,顾名思义,记录次数,比如对人的年龄或者种类等一些
序数的属性,它的取值范围十分有限,年龄0-120岁,这时候不放考虑空间换时间,开辟一个hashtable(数组,vector啥的都行)
key是排序的值,value为对应的元素。就开辟N个桶,里面放下东西即可。
class CountingSort {
public:
int* countingSort(int* A, int n) {
if(A == NULL || n < 2)
return A;
int max = 0, min = A[0];
for(int i = 0; i < n; ++i){
if(A[i] > max) max = A[i];
if(A[i] < min) min = A[i];
}
int len = max - min + 1;
vector<vector<int> > res(len); //用二维的vector来代表每个桶
for(int i = 0; i < n; ++i){
res[A[i] - min].push_back(A[i]);
}
int count = 0;
for(int i = 0; i < len; i++){
if(!res[i].empty()){
for(vector<int>::iterator itr = res[i].begin(); itr != res[i].end(); ++itr){
A[count++] = *itr;
}
}
}
return A;
}
};
2.11 基数排序
基数排序,是用10个桶,编号为0-9,其中每个桶的编号数字代表了一个要排序的数的个位数字。
第一次按照最低位,把每一个数放到不同的桶里面。
之后,按照桶编号的顺序把桶里面的每一个数字倒出来,再按照第二位的数放到不同编号的桶里面,(个位数字放在零号桶)
重复以上过程,直到
最大的那个数的最高位也放入到相应的桶中。
这里用一个二维的vector数组来实现10个不同的桶。
class RadixSort {
public:
int getDigit(int n){
int count=0;
while(n>0){
n/=10;
count++;
}
return count;
}
int getSingleDigit(int n,int pos){
int i=1;
while(i<pos){
n=n/10;
i++;
}
return n%10;
}
int* radixSort(int* A, int n) {
// write code here
if(A == NULL || n < 2)
return A;
int max=A[0];
for(int i=1;i<n;++i)
if(A[i]>=max)
max = A[i];
int maxDigit= getDigit(max);
int index = 1;
vector<vector<int> > res(10);
int dig;
int k= 0;
while(index<=maxDigit){ //index means the digit bit of each number
k = 0;
for(int i=0;i<n;i++){
dig = getSingleDigit(A[i],index);
res[dig].push_back(A[i]);
}
for(int i=0;i<10;i++){
for(int j=0;j<res[i].size();j++)
{
A[k++] = res[i][j];
}
res[i].clear();
}
index++;
}
return A;
}
};
参考文献:1,百度百科
3,大话数据结构