六、鸡尾酒排序/双向冒泡排序
3)算法图解
4)算法代码
void CocktailSort(int *a,int nsize)
{
int tail=nsize-1;
for (int i=0;ii;--j) //第一轮,先将最小的数据排到前面
{
if (a[j]a[j+1])
{
int temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
}
}
tail--; //原tail处数据也已排好序,将其减1
}
}
5)考察点,重点和频度分析考点基本类似冒泡排序,请参考《数据结构基础 排序算法(一) 概念篇》最后一节。
详见:数据结构基础 排序算法(一) 概念篇
详址:http://blog.csdn.net/u013630349/article/details/47211257
七、快速排序
恩,重头戏开始了,快速排序是各种笔试面试最爱考的排序算法之一,且排序思想在很多算法题里面被广泛使用。是需要重点掌握的排序算法。function quicksort(q)
var list less, pivotList, greater
if length(q) ≤ 1 {
return q
} else {
select a pivot value pivot from q
for each x in q except the pivot element
if x < pivot then add x to less
if x ≥ pivot then add x to greater
add pivot to pivotList
return concatenate(quicksort(less), pivotList, quicksort(greater))
}
在平均状况下,排序 n 个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n^2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。3)算法图解
快速排序会递归地进行很多轮,其中每一轮称之为快排的partition算法,即上述算法描述中的第2步,非常重要,且在各种笔试面试中用到该思想的算法题层出不穷,下图为第一轮的partition算法的一个示例。
4)算法代码我们选取数组的第一个元素作为主元,每一轮都是和第一个元素比较大小,通过交换,分成大于和小于它的前后两部分,再递归处理。代码如下
/**************************************************
函数功能:对数组快速排序
函数参数:指向整型数组arr的首指针arr;
整型变量left和right左右边界的下标
函数返回值:空
/**************************************************/
void QuickSort(int *arr, int left, int right)
{
int i,j;
if(leftarr[0] && i
2、版本二
随机选基准数的快排//使用引用,完成两数交换
void Swap(int& a , int& b)
{
int temp = a;
a = b;
b = temp;
}
//取区间内随机数的函数
int Rand(int low, int high)
{
int size = hgh - low + 1;
return low + rand()%size;
}
//快排的partition算法,这里的基准数是随机选取的
int RandPartition(int* data, int low , int high)
{
swap(data[rand(low,high)], data[low]);//
int key = data[low];
int i = low;
for(int j=low+1; j<=high; j++)
{
if(data[j]<=key)
{
i = i+1;
swap(data[i], data[j]);
}
}
swap(data[i],data[low]);
return i;
}
//递归完成快速排序
void QuickSort(int* data, int low, int high)
{
if(low
5)考察点,重点和频度分析void GetLeastNumbers_by_partition(int* input, int n, int* output, int k)
{
if(input == NULL || output == NULL || k > n || n <= 0 || k <= 0)
return;
int start = 0;
int end = n - 1;
int index = Partition(input, n, start, end);
while(index != k - 1)
{
if(index > k - 1)
{
end = index - 1;
index = Partition(input, n, start, end);
}
else
{
start = index + 1;
index = Partition(input, n, start, end);
}
}
for(int i = 0; i < k; ++i)
output[i] = input[i];
}
例题2判断数组中出现超过一半的数字
当然,这道题很多人都见过,而且最通用的一种解法是数对对消的思路。这里只是再给大家提供一种思路,快排partition的方法在很多地方都能使用,比如这题。我们也可以选择合适的判定条件进行递归。代码如下:
bool g_bInputInvalid = false;
bool CheckInvalidArray(int* numbers, int length)
{
g_bInputInvalid = false;
if(numbers == NULL && length <= 0)
g_bInputInvalid = true;
return g_bInputInvalid;
}
bool CheckMoreThanHalf(int* numbers, int length, int number)
{
int times = 0;
for(int i = 0; i < length; ++i)
{
if(numbers[i] == number)
times++;
}
bool isMoreThanHalf = true;
if(times * 2 <= length)
{
g_bInputInvalid = true;
isMoreThanHalf = false;
}
return isMoreThanHalf;
}
int MoreThanHalfNum_Solution1(int* numbers, int length)
{
if(CheckInvalidArray(numbers, length))
return 0;
int middle = length >> 1;
int start = 0;
int end = length - 1;
int index = Partition(numbers, length, start, end);
while(index != middle)
{
if(index > middle)
{
end = index - 1;
index = Partition(numbers, length, start, end);
}
else
{
start = index + 1;
index = Partition(numbers, length, start, end);
}
}
int result = numbers[middle];
if(!CheckMoreThanHalf(numbers, length, result))
result = 0;
return result;
}
例题3#include
using namespace std;
void Proc( char *str )
{
int i = 0;
int j = 0;
//移动指针i, 使其指向第一个大写字母
while( str[i] != '\0' && str[i] >= 'a' && str[i] <= 'z' ) i++;
if( str[i] != '\0' )
{
//指针j遍历未处理的部分,找到第一个小写字母
for( j=i; str[j] != '\0'; j++ )
{
if( str[j] >= 'a' && str[j] <= 'z' )
{
char tmp = str[i];
str[i] = str[j];
str[j] = tmp;
i++;
}
}
}
}
int main()
{
char data[] = "SONGjianGoodBest";
Proc( data );
return 0;
}
建堆是一个通过不断的堆调整,使得整个二叉树中的数满足堆性质的操作。在数组中的话,我们一般从下标为n/2的数开始做堆调整,一直到下标为0的数(因为下标大于n/2的数都是叶子节点,其子树已经满足堆的性质了)。下图为其一个图示:
数组储存成堆的形式之后,第一次将A[0]与A[n - 1]交换,再对A[0…n-2]重新恢复堆。第二次将A[0]与A[n-2]交换,再对A[0…n-3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最小的数据并入到后面的有序区间,故操作完成后整个数组就有序了。
详见:数据结构基础 排序 之 二叉堆实现堆排序
详址:http://blog.csdn.net/u013630349/article/details/46906969#comments
最差时间复杂度,O(n log n)3)算法图解
详见:数据结构基础 排序 之 二叉堆实现堆排序
详址:http://blog.csdn.net/u013630349/article/details/46906969#comments
4)算法代码直接上代码吧,重点注意HeapAdjust,BuildHeap和HeapSort的实现。
#include
#include
#include
using namespace std;
int parent(int);
int left(int);
int right(int);
void HeapAdjust(int [], int, int);
void BuildHeap(int [], int);
void print(int [], int);
void HeapSort(int [], int);
/*返回父节点*/
int parent(int i)
{
return (int)floor((i - 1) / 2);
}
/*返回左孩子节点*/
int left(int i)
{
return (2 * i + 1);
}
/*返回右孩子节点*/
int right(int i)
{
return (2 * i + 2);
}
/*对以某一节点为根的子树做堆调整(保证最大堆性质)*/
void HeapAdjust(int A[], int i, int heap_size)
{
int l = left(i);
int r = right(i);
int largest;
int temp;
if(l < heap_size && A[l] > A[i])
{
largest = l;
}
else
{
largest = i;
}
if(r < heap_size && A[r] > A[largest])
{
largest = r;
}
if(largest != i)
{
temp = A[i];
A[i] = A[largest];
A[largest] = temp;
HeapAdjust(A, largest, heap_size);
}
}
/*建立最大堆*/
void BuildHeap(int A[],int heap_size)
{
for(int i = (heap_size-2)/2; i >= 0; i--)
{
HeapAdjust(A, i, heap_size);
}
}
/*输出结果*/
void print(int A[], int heap_size)
{
for(int i = 0; i < heap_size;i++)
{
printf("%d ", A[i]);
}
printf("\n");
}
/*堆排序*/
void HeapSort(int A[], int heap_size)
{
BuildHeap(A, heap_size);
int temp;
for(int i = heap_size - 1; i >= 0; i--)
{
temp = A[0];
A[0] = A[i];
A[i] = temp;
HeapAdjust(A, 0, i);
}
print(A, heap_size);
}
/*测试,对给定数组做堆排序*/
int main(int argc, char* argv[])
{
const int heap_size = 13;
int A[] = {19, 1, 10, 14, 16, 4, 7, 9, 3, 2, 8, 5, 11};
HeapSort(A, heap_size);
system("pause");
return 0;
}
5)考察点,重点和频度分析#include "stdafx.h"
#include
#include
#include
#include // for greater<>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
vector bigs(10000,0);
vector::iterator it;
// Init vector data
for (it = bigs.begin(); it != bigs.end(); it++)
{
*it = (float)rand()/7; // random values;
}
cout << bigs.size() << endl;
make_heap(bigs.begin(),bigs.end(), greater()); // The first one is the smallest one!
float ff;
for (int i = 0; i < 1000000000; i++)
{
ff = (float) rand() / 7;
if (ff > bigs.front()) // replace the first one ?
{
// set the smallest one to the end!
pop_heap(bigs.begin(), bigs.end(), greater());
// remove the last/smallest one
bigs.pop_back();
// add to the last one
bigs.push_back(ff);
// mask heap again, the first one is still the smallest one
push_heap(bigs.begin(),bigs.end(),greater());
}
}
// sort by ascent
sort_heap(bigs.begin(), bigs.end(), greater());
return 0;
}
例题23)算法图解
4)算法代码
//将有二个有序数列a[first...mid]和a[mid...last]合并。
void MergeArray(int a[], int first, int mid, int last, int temp[])
{
int i = first, j = mid + 1;
int m = mid, n = last;
int k = 0;
while (i <= m && j <= n)
{
if (a[i] <= a[j])
temp[k++] = a[i++];
else
temp[k++] = a[j++];
}
while (i <= m)
temp[k++] = a[i++];
while (j <= n)
temp[k++] = a[j++];
for (i = 0; i < k; i++)
a[first + i] = temp[i];
}
//递归地完成归并排序
void MergeSort(int a[], int first, int last, int temp[])
{
if (first < last)
{
int mid = (first + last) / 2;
mergesort(a, first, mid, temp); //左边有序
mergesort(a, mid + 1, last, temp); //右边有序
mergearray(a, first, mid, last, temp); //再将二个有序数列合并
}
}
5)考察点、重点和频度分析#include
#include
using namespace std;
void printArray(int arry[],int len)
{
for(int i=0;i=start&&j>mid)
{
if(arry[i]>arry[j])
{
temp[k++]=arry[i--];//从临时数组的最后一个位置开始排序
count+=j-mid;//因为arry[mid+1...j...end]是有序的,如果arry[i]>arry[j],那么也大于arry[j]之前的元素,从a[mid+1...j]一共有j-(mid+1)+1=j-mid
}
else
{
temp[k++]=arry[j--];
}
}
cout<<"调用MergeArray时的count:"<=start)//表示前半段数组中还有元素未放入临时数组
{
temp[k++]=arry[i--];
}
while(j>mid)
{
temp[k++]=arry[j--];
}
//将临时数组中的元素写回到原数组当中去。
for(i=0;i
例题2#include
#include
#include
#include
using namespace std;
void insert_sort(int arr[],int n)
{
for(int i=1;ival&&j>=0)
{
arr[j+1]=arr[j];
--j;
}
arr[j+1]=val;
}
}
void aux_merge(int arr[],int n,int m,int aux[])
{
for(int i=0;i=0)
{
if(q>=0)
{
if(arr[p]>aux[q])
{
swap(arr[p],arr[dst]);
p--;
}
else
{
swap(aux[q],arr[dst]);
q--;
}
}
else
break;
}
else
{
swap(aux[q],arr[dst]);
q--;
}
dst--;
}
}
void local_merge(int arr[],int n)
{
int m=sqrt((float)n);
int k=n/m;
for(int i=0;i
3、对每个不是空的桶子进行排序。
4、从不是空的桶子里把项目再放回原来的串行中。
3)算法图解
4)算法代码
#include
#include
#include
using namespace std;
/*initial arr*/
void InitialArr(double *arr,int n)
{
srand((unsigned)time(NULL));
for (int i = 0; i 0 && temp < bucket[flag][j - 1]; --j)
{
bucket[flag][j] = bucket[flag][j-1];
}
bucket[flag][j] =temp;
}
/* 所有数据重新链接 */
int k=0;
for (int i = 0 ; i < 10 ; i++)
{
for (int j = 0 ; j< count[i];j++)
{
arr[k] = bucket[i][j];
k++;
}
}
for (int i = 0 ; i<10 ;i++)
{
delete bucket[i];
bucket[i] =NULL;
}
delete []bucket;
bucket = NULL;
}
void main()
{
double *arr=new double[10];
InitialArr(arr, 10);
BucketSort(arr, 10);
PrintArr(arr,10);
delete [] arr;
}
5)考察点、重点和频度分析//距离平均值为offset = (arrayMax - arrayMin) / (n - 1), 则距离最大的数必然大于这个值
//每个桶只要记住桶中的最大值和最小值,依次比较上一个桶的最大值与下一个桶的最小值的差值,找最大的即可.
#include
#define MAXSIZE 100 //实数的个数
#define MAXNUM 32767
using namespace std;
struct Barrel
{
double min; //桶中最小的数
double max; //桶中最大的数
bool flag; //标记桶中有数
};
int BarrelOperation(double* array, int n)
{
Barrel barrel[MAXSIZE]; //实际使用的桶
int nBarrel = 0; //实际使用桶的个数
Barrel tmp[MAXSIZE]; //临时桶,用于暂存数据
double arrayMax = -MAXNUM, arrayMin = MAXNUM;
for(int i = 0; i < n; i++) {
if(array[i] > arrayMax)
arrayMax = array[i];
if(array[i] < arrayMin)
arrayMin = array[i];
}
double offset = (arrayMax - arrayMin) / (n - 1); //所有数的平均间隔
//对桶进行初始化
for(i = 0; i < n; i++) {
tmp[i].flag = false;
tmp[i].max = arrayMin;
tmp[i].min = arrayMax;
}
//对数据进行分桶
for(i = 0; i < n; i++) {
int pos = (int)((array[i] - arrayMin) / offset);
if(!tmp[pos].flag) {
tmp[pos].max = tmp[pos].min = array[i];
tmp[pos].flag = true;
} else {
if(array[i] > tmp[pos].max)
tmp[pos].max = array[i];
if(array[i] < tmp[pos].min)
tmp[pos].min = array[i];
}
}
for(i = 0; i <= n; i++) {
if(tmp[i].flag)
barrel[nBarrel++] = tmp[i];
}
int maxOffset = 0.0;
for(i = 0; i < nBarrel - 1; i++) {
if((barrel[i+1].min - barrel[i].max) > maxOffset)
maxOffset = barrel[i+1].min - barrel[i].max;
}
return maxOffset;
}
int main()
{
double array[MAXSIZE] = {1, 8, 6, 11, 7, 13, 16, 5}; //所需处理的数据
int n = 8; //数的个数
//double array[MAXSIZE] = {8, 6, 11};
//int n = 3;
int maxOffset = BarrelOperation(array, n);
cout << maxOffset << endl;
return 0;
}
3)算法图解
我们使用计数排序对一个乱序的整数数组进行排序。
首先创建一个临时数组(长度为输入数据的最大间隔),对于每一个输入数组的整数k,我们在临时数组的第k位置"1"。如下图
上图中,第一行表示输入数据,第二行表示创建的临时数据,临时数组的下标代表输入数据的某一个值,临时数组的值表示输入数据中某一个值的数量。
如果输入数据中有重复的数值,那么我们增加临时数组相应的值(比如上图中5有3个,所以小标为5的数组的值是3)。在“初始化”临时数组以后,我们就得到了一个排序好的输入数据。
我们顺序遍历这个数组,将下标解释成数据, 将该位置的值表示该数据的重复数量,记得得到一个排序好的数组。
4)算法代码
#include
#include
#include
/**************************************************************
功能:计数排序。
参数: data : 要排序的数组
size :数组元素的个数
k :数组中元素数组最大值 +1 (这个需要+1)
返回值: 成功0;失败-1.
*************************************************************/
int ctsort(int *data, int size, int k)
{
int * counts = NULL,/*计数数组*/
* temp = NULL;/*保存排序后的数组*/
int i = 0;
/*申请数组空间*/
if ((counts = (int *) malloc( k * sizeof(int))) == NULL)
return -1;
if ((temp = (int *) malloc( k * sizeof(int))) == NULL)
return -1;
/*初始化计数数组*/
for (i = 0; i < k; i ++)
counts[i] = 0;
/*数组中出现的元素,及出现次数记录*/
for(i = 0; i < size; i++)
counts[data[i]] += 1;
/*调整元素计数中,加上前一个数*/
for (i = 1; i < k; i++)
counts[i] += counts[i - 1];
/*使用计数数组中的记录数值,来进行排序,排序后保存的temp*/
for (i = size -1; i >= 0; i --){
temp[counts[data[i]] - 1] = data[i];
counts[data[i]] -= 1;
}
memcpy(data,temp,size * sizeof(int));
free(counts);
free(temp);
return 0;
}
int main()
{
int a[8] = {2,0,2,1,4,6,7,4};
int max = a[0],
i = 0;
/*获得数组中中的数值*/
for ( i = 1; i < 8; i++){
if (a[i] > max)
max = a[i];
}
ctsort(a,8,max+1);
for (i = 0;i < 8;i ++)
printf("%d\n",a[i]);
}
5)考察点、重点和频度分析十二、基数排序
1)算法简介3)算法图解
4)算法代码#include
#include
void radixSort(int data[]) {
int temp[10][10] = {0};
int order[10] = {0};
int n = 1;
while(n <= 10) {
int i;
for(i = 0; i < 10; i++) {
int lsd = ((data[i] / n) % 10);
temp[lsd][order[lsd]] = data[i];
order[lsd]++;
}
// 重新排列
int k = 0;
for(i = 0; i < 10; i++) {
if(order[i] != 0) {
int j;
for(j = 0; j < order[i]; j++, k++) {
data[k] = temp[i][j];
}
}
order[i] = 0;
}
n *= 10;
}
}
int main(void) {
int data[10] = {73, 22, 93, 43, 55, 14, 28, 65, 39, 81};
printf("\n排序前: ");
int i;
for(i = 0; i < 10; i++)
printf("%d ", data[i]);
putchar('\n');
radixSort(data);
printf("\n排序後: ");
for(i = 0; i < 10; i++)
printf("%d ", data[i]);
return 0;
}
5)考察点、重点和频度分析【小结】
【参考详址】
http://blog.csdn.net/han_xiaoyang/article/details/12163251
http://www.cnblogs.com/gaochundong/p/comparison_sorting_algorithms.html#merge_sort
http://blog.csdn.net/itcastcpp/article/details/13622689
【详细代码】
详见:各种经典排序算法汇总( 亲测调试运算通过)
详址:http://blog.csdn.net/woshioosm/article/details/7367026
【STL::Sort】
详见:对vector等STL标准容器进行排序操作
详址:http://blog.csdn.net/ihadl/article/details/7400929
【记忆方法】
怎么记忆稳定性:
总共四大类排序:插入、选择、交换、归并(基数排序暂且不算)
比较高级一点的(时间复杂度低一点得)shell排序,堆排序,快速排序(除了归并排序)都是不稳定的,在加上低一级的选择排序是不稳定的。
比较低级一点的(时间复杂度高一点的)插入排序,冒泡排序,归并排序,基数排序都是稳定的。
(4种不稳定,4种稳定)。