算法描述
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
1)从数列中挑出一个元素,称为 “基准”(pivot);
2)重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
3)递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
C语言实现: 自己写的
//A待排序数组, len:数组长度
void QuickSort(int A[], int len)
{
if(len <= 1)
return;
int pivotIdx = 0; //选择最左侧的元素作为基准
int nextPutIdx = pivotIdx+1; //如果发现有比pivot小的元素,则直接与A[nextPutIdx]交换
for(int idx = nextPutIdx; idx < len; idx++)
{
if(A[idx] < A[pivotIdx])
{
swap(A,idx,nextPutIdx);
//交换之后,更新nextPutIdx,保证A[pivotIdx+1],A[pivotIdx+2],...,A[nextPutIdx-1]都比A[pivot]小.
nextPutIdx++;
}
}
//下面两行使得A[pivotIdx]左侧的都比A[pivotIdx]小,右侧的都比A[pivotIdx]大。
swap(A,pivotIdx, nextPutIdx-1);
pivotIdx = nextPutIdx-1;
QuickSort(A,pivotIdx); //继续递归左半部分
QuickSort(&A[pivotIdx+1],len-pivotIdx-1);//继续递归右半部分
}
注意,上面实现的细节,面试时被问到过一次如果实现一次递归,把比pivot小的都移到左边,忘了,结果死在这个地方。
思想是快慢指针!! 知道了思想就不会忘记了!
表述: 以最左侧的元素为基准,定义两个指针slow, fast,slow指针指向基准后面的第一个元素,fast指针指向基准后面的第二个元素,然后,从fast指针位置开始遍历数组,如果fast元素比pivot大或相等,则不动作,只是fast++,,如果fast元素比pivot小,则将两者指向的元素互换,并且fast++,slow++。
遍历完成后,将pivot值(整个遍历过程中pivot没有参与)与slow元素互换,over!
快速排序最差的情况对应的时间复杂度:
--之前被问到,没反应过来,死了。
以最左端的pivot为基准完成一次后,所有的元素都在pivot左边,即所有元素都比pivot小,然后,对pivot左边的这部分进行动作时,又是同样的情况,则时间为n+(n-1)/2*2 +(n-2)/2*2 + ... +1,所以o(n^2)
快速排序最好的情况对应的时间复杂度:
已经排好序了,然后,每次对相应的部分进行时,虽然没有元素的交换,只有比较,但是也要遍历一遍,古为o(n).
工程实践中,关注的是一个算法的最坏、最好时间复杂度,而不是“平均”, 在工程时间中关注的是稳定性,所以常用堆排序。 在工程中o(n^2)的时间复杂度是不能容忍的! -- 天翼工程师
算法描述
归并排序是建立在归并操作上的一种有效的排序算法。 该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
步骤:
注意:递归的终止条件是待组合的两个子序列的长度都为1.
归并排序在超大文件排序中的应用: https://blog.csdn.net/qq_35865125/article/details/106309726
自己写的代码:
//实现归并排序,分治,递归
void MergeSort(int a[], int stIdx, int endIdx)
{
int midIdx = (endIdx + stIdx)/2;//I ever :(endIdx - stIdx)/2!!
if(endIdx - stIdx > 1)
{
MergeSort(a, stIdx, midIdx);
MergeSort(a, midIdx+1, endIdx);
merge(a,stIdx,midIdx,endIdx);//Do not forget it. I ever made mistake
}
else if(endIdx - stIdx == 1)
{
merge(a,stIdx,midIdx,endIdx);
return;
}
else if(endIdx == stIdx)
{
return;
}
}
//A[stIdx], ... ,A[midIdx]已经完成从小到大的排序
//A[midIdx+1], ..., A[endIdx]已经完成从小到大的排序
//该函数将这两段合并,从小到大的顺序
void merge(int A[],int stIdx, int midIdx,int endIdx)
{
int totalLen = endIdx - stIdx + 1;
int* array = (int*)malloc(sizeof(int)*totalLen );
int arrayIdx = 0;
int idx1 = stIdx; int idx2 = midIdx+1;
while((idx1 <= midIdx)||(idx2 <= endIdx))//
{
if((idx1 <= midIdx)&&(idx2 <= endIdx))
{
if(A[idx1] <= A[idx2])
{
array[arrayIdx] = A[idx1];
idx1++;
}
else
{
array[arrayIdx] = A[idx2];
idx2++;
}
arrayIdx++;
}
else if((idx1 <= midIdx))
{
for(int i = idx1; i <= midIdx;i++ )
{
array[arrayIdx] = A[i];
arrayIdx++;
}
break;
}
else if(idx2 <= endIdx)
{
for(int i = idx2; i <= endIdx;i++ )
{
array[arrayIdx] = A[i];
arrayIdx++;
}
break;
}
}
for(int idx = stIdx;idx<=endIdx;idx++)
{
A[idx] = array[idx-stIdx];
}
delete array;
}
归并排序的时间复杂度:O(n*log(n))
https://blog.csdn.net/qq_32534441/article/details/95098059
T(n)=2*T(n/2) + n;.....
好久没有写,以为一分钟就写出来,没想到还是想了一会儿,岁月让我忘记了它/
//排成从小到大的顺序
void BubbleSort(int a[], int len)
{
for(int idx1 = 0;idx1 < len; idx1++)
{
int flag = 1;
for(int idx2 = 1; idx2 < len; idx2++)//遍历一遍后,最大的数会沉到最底
{
if(a[idx2] < a[idx2 -1])
{
swap(a, idx2,idx2-1);
}
flag = 0;
}
if(flag)
break;//如果遍历时,一次交换也没有发生,则说明已经排好了
}
}
选择排序的一种, 可利用数组的特点快速定位指定索引的元素。
堆的概念:
堆分为两种:最大堆和最小堆,两者的差别在于节点的排序方式。
在最大堆中,父节点的值比每一个子节点的值都要大。在最小堆中,父节点的值比每一个子节点的值都要小。这就是所谓的“堆属性”,并且这个属性对堆中的每一个节点都成立。
上图是一个最大堆,每一个父节点的值都比其子节点要大。10
比 7
和 2
都大,7
比 5
和 1
都大。
最大堆总是将其中的最大值存放在树的根节点, 最小堆的根节点元素总是树中的最小值。从而可以快速的访问到“最重要”的元素。
堆排序讲解:
https://www.cnblogs.com/chengxiao/p/6129630.html
https://www.jianshu.com/p/0d383d294a80
https://blog.csdn.net/weixin_42250655/article/details/82878317
时间复杂度:
堆排序在最坏情况下,其时间复杂度也为O(nlogn)。
由构建初始堆+交换堆顶元素和末尾元素并重建堆两部分组成。其中构建初始堆经推导复杂度为O(n),在交换并重建堆的过程中,需交换n-1次,而重建堆的过程中,根据完全二叉树的性质,[log2(n-1),log2(n-2)...1]逐步递减,近似为nlogn。所以堆排序时间复杂度一般认为就是O(nlogn)级。
Mycode:
#include
class CHeapSort {
public:
//nodeIdx对应的元素为子树的根节点, 除了根节点之外,该子树的其他元素符合最大堆定义。
//通过该函数将这棵子树重新调整为符合最大堆定义的子树,即将根节点放在合适位置。
void adjusttHeap(int array[], const int len, const int nodeIdx)
{
int fatherIdx = nodeIdx;
int lChildIdx = 2 * fatherIdx + 1; //左孩子的索引
int rChildIdx = lChildIdx + 1; //右孩子的索引
int biggerChildIdx = lChildIdx; //指示哪个孩子更大
while (fatherIdx * 2 + 1 < len)//一直到叶子节点
{
if (lChildIdx < len - 1 && array[lChildIdx] < array[rChildIdx])//右孩子更大
biggerChildIdx = rChildIdx;
if (biggerChildIdx > len - 1)
break;
if (array[fatherIdx] < array[biggerChildIdx]) // 如果父节点比孩子小. 最终array数组的后端是小值
{
//array[fatherIdx] = array[biggerChildIdx];
swap(array, fatherIdx, biggerChildIdx);
}
else{
//array[fatherIdx] = array[nodeIdx];
break;
}
fatherIdx = biggerChildIdx;
lChildIdx = 2 * fatherIdx + 1; //左孩子的索引
rChildIdx = lChildIdx + 1; //右孩子的索引
}
}
void swap(int aray[], int i, int j)
{
int tmp = aray[i]; aray[i] = aray[j];
aray[j] = tmp;
}
void heapSort(int array[], int len)
{
//初始化堆
//从树的最下方的非叶子节点开始调整
//array[fatherNodeIdx*2+1]对应fatherNodeIdx的左子树
for (int fatherNodeIdx = len / 2; fatherNodeIdx >= 0; fatherNodeIdx--)//array[0]对应的是整个树的根节点呀
{
adjusttHeap(array, len, fatherNodeIdx);
}
//排序,不断地将堆顶部元素移动到array的最后,即,将堆顶元素与树的最下层的最右侧的叶子交换
//然后,调整数
for (int lastIdx = len - 1; lastIdx >= 0; lastIdx--)
{
//交换
int temp = array[0];
array[0] = array[lastIdx];
array[lastIdx] = temp;
int unsortedLen = lastIdx;
adjusttHeap(array, unsortedLen, 0);
}
//Now,array 中存储着有序的序列。
}
};
这个testCase有误:
CHeapSort objHeapSort;
//int a[4] = { 1, 3, 2,-9 };
//int n = 4;
int a[10] = { 1, 3, 2, 9 ,6 ,8, 7, 5, 0, 4 };
int n = 10;
printf("原数组为: \n");
for (int i = 0; i < n; i++)
printf("%d ", a[i]);
printf("\n");
objHeapSort.heapSort(a, n);
printf("排序后的数组为:\n ");
for (int i = 0; i < n; i++)
printf("%d\t", a[i]);
printf("\n");
Ref:
https://www.cnblogs.com/onepixel/articles/7674659.html