排序
一、排序的基本概念
排序:简而言之,就是按照一定顺序实现数据集合内元素的有序。
算法的稳定性:如果待排序表中有两个元素Ri和Rj,对应关键字keyi=keyj,且排序前,Ri在Rj前面;排序后,Ri仍在Rj前面,则称这个算法使稳定的。
二、插入排序
插入排序的基本思想:将一个待排序的元素按关键字大小插入到一个已经排序好的子序列,直到全部元素插入完成。
(1)直接插入排序
有序序列L[0…i-1] | L[i] | 无序序列L[i+1…n-1] |
---|
将元素L[i]插入到有序序列L[0…i-1]当中,需要执行以下操作:
1)查找在L[0…i-1]中要插入的位置K
2)将L[k…i-1]元素后移一个位置
3)将L[i]插入到L[k]上
template<class T>
void InsertSort(T *arry, int n)
{
int i, j;
for (i = 1; i < n; i++) //将arry[1...n-1]元素依次插入到有序序列当中
{
int temp = arry[i]; //记录当前插入元素
for (j = i - 1; temp < arry[j]; j--) //当前要插入元素和已经排序好的子序列元素进行比较,从后向前
{
arry[j+1] = arry[j]; //如果插入元素小于子序列最后一个元素,子序列元素后移
}
arry[j + 1] = temp;//将插入元素放入要插入的位置
}
}
int arry[8] = { 49,38,65,97,76,13,27,49 };
输出:
第1遍排序:38 49 65 97 76 13 27 49
第2遍排序:38 49 65 97 76 13 27 49
第3遍排序:38 49 65 97 76 13 27 49
第4遍排序:38 49 65 76 97 13 27 49
第5遍排序:13 38 49 65 76 97 27 49
第6遍排序:13 27 38 49 65 76 97 49
第7遍排序:13 27 38 49 49 65 76 97
直接插入排序算法性能分析:
空间效率:使用了常数个辅助单元,空间复杂度O(1)
时间效率:向子序列插入元素n-1躺,每趟操作分为移动元素和比较关键字。
最好的情况下,元素已经有序,只需要比较,不需要移动,时间复杂度O(n)。
最坏情况下:元素逆序。
平均时间复杂度:O(n2)
稳定性:从后先前比较,不会出现元素相对位置出现变化,所以是稳定的。
(2)折半插入排序
折半插入排序基本思想:在直接插入排序上,把比较和移动分离,先找到要插入的位置,再进行元素位置的移动。
int Fold(int* arry, int n,int key)
{
int low = 0;
int high = n - 1;
while (low<=high)
{
int mid = (low + high) / 2; //取中间点
if (arry[mid] > key) //中间点的值大于插入值
{
high = mid-1; //向左半子表继续查找
}
else
{
low = mid+1; //向右半子表继续查找
}
}
return low; //返回要插入的位置
}
void InsertSort(int* arry, int n)
{
int i, j;
for (i = 1; i < n; i++) //将arry[1...n-1]元素依次插入到有序序列当中
{
int temp = arry[i]; //记录当前插入元素
int k = Fold(arry, i, temp); //找到要插入的位置
for (j = i - 1; j >= k; j--) //子序列元素后移
{
arry[j + 1] = arry[j];
}
arry[k] = temp;//将插入元素放入要插入的位置
}
}
(3)希尔排序
增量排序,划分小的子序列,然后对子序列进行直接插入排序。
template<class T>
void ShellSort(T * arry,int n)
{
for (int size = n/2; size >= 1; size=size/2) //步长
{
for (int i = size ; i < n; i++) //步长前面的元素相当于子序列
{
if (arry[i] < arry[i - size]) //如果当前元素小于步长前面的子序列元素,进行直接插入排序
{
T temp = arry[i];
int j = 0;
for ( j = i - size; j >= 0 && temp < arry[j]; j = j - size)//寻找插入点
{
arry[j + size] = arry[j];
}
arry[j + size] = temp; //插入元素
}
}
}
}
输出:
int arry[8] = { 49,38,65,97,76,13,27,49 };
第1遍排序:49 13 27 49 76 38 65 97
第1遍排序:27 13 49 38 65 49 76 97
第1遍排序:13 27 38 49 49 65 76 97
空间效率:使用了常数个辅组单元,空间复杂度O(1)
时间效率:n在某个特定范围,时间复杂度O(n1.3),最坏情况下:O(n2)
稳定性:不稳定
三、交换排序
(1)冒泡排序
基本思想:从前往后(从后往前)两两比较相邻元素的值,如果A[i-1]>A[i],交换位置,直到序列排完。最多n-1趟冒泡就可以排序好数组。
template<class T>
void BubbleSort(T* arry, int n)
{
for (int i = 0; i < n-1; i++) //最多n-1趟冒泡排序
{
bool flag = false; //本趟冒泡排序是否发生交换的标志
for (int j = n - 1; j > i; j--) //从后向前冒泡
{
if (arry[j - 1] > arry[j])
{
swap(arry[j - 1], arry[j]); //交换
flag = true;
}
}
if (flag == false) //没有交换,就表示元素已经有序
return;
}
}
输出:
int arry[8] = { 49,38,65,97,76,13,27,49 };
没有加flag
第1遍排序:13 49 38 65 97 76 27 49
第2遍排序:13 27 49 38 65 97 76 49
第3遍排序:13 27 38 49 49 65 97 76
第4遍排序:13 27 38 49 49 65 76 97
第5遍排序:13 27 38 49 49 65 76 97
第6遍排序:13 27 38 49 49 65 76 97
第7遍排序:13 27 38 49 49 65 76 97
加flag
第1遍排序:13 49 38 65 97 76 27 49
第2遍排序:13 27 49 38 65 97 76 49
第3遍排序:13 27 38 49 49 65 97 76
第4遍排序:13 27 38 49 49 65 76 97
空间效率:常数个辅组单元,空间复杂度O(1)
时间效率:最坏情况下为O(n2),最好为O(n)。平均为O(n2)
稳定性:稳定
(2)快速排序
快速排序的基本思想是基于分治法的:在待排序的表L[1…n]中人去一个元素pivot作为枢纽(通常取首元素),通过一趟快速排序将表划分成两个部分L[1…k-1]和L[k+1…n]。L[1…k-1]中元素都小于pivot和L[k+1…n]中元素都大于pivot,L[k]放pivot。
template<class T>
int Partition(T* arry, int low, int high)
{
T pivot = arry[low]; //当前元素第一个当作pivot
while (low < high)
{
while (low < high && arry[high] >= pivot)
high--;
arry[low] = arry[high]; //将比枢轴元素小的元素放到左端
while (low < high && arry[low] <= pivot)
low++;
arry[high] = arry[low]; //将比枢轴元素大的元素放到右端
}
arry[low] = pivot; //将枢轴放到最终位置
return low; //返回枢轴最终存放的位置
}
template<class T>
void QuickSort(T* arry, int low, int high)
{
if (low < high) //递归跳出条件
{
int pivotpos = Partition(arry, low, high); //划分
QuickSort(arry, low, pivotpos - 1); //对子表递归
QuickSort(arry, pivotpos+1, high);
}
}
输出:
int arry[8] = { 49,38,65,97,76,13,27,49 };
第1遍排序:27 38 13 49 76 97 65 49
第2遍排序:13 27 38 49 76 97 65 49
第3遍排序:13 27 38 49 49 65 76 97
第4遍排序:13 27 38 49 49 65 76 97
空间效率:O(log2n)
时间效率:O(nlog2n)
稳定性:不稳定
快速排序是所有内部排序算法中平均性能最优的算法。
四、选择排序
选择排序的基本思想是:每一趟(如第i趟)在后面n-i+1个待排序元素中找到最小元素,作为有序序列第i个元素。
(1)简单选择排序
template<class T>
void SelectSort(T* arry, int n)
{
for (int i = 0; i < n - 1; i++) //一共n-1趟
{
int min = i; //记录最小元素位置
for (int j = i + 1; j < n; j++)//在位置[i+1...n-1]中找到最小元素位置
{
if (arry[j] < arry[min])
{
min = j;
}
}
swap(arry[i], arry[min]);//交换min元素到位置i,把i位置元素交换到min处
}
}
输出:
int arry[8] = { 49,38,65,97,76,13,27,49 };
第1遍排序:13 38 65 97 76 49 27 49
第2遍排序:13 27 65 97 76 49 38 49
第3遍排序:13 27 38 97 76 49 65 49
第4遍排序:13 27 38 49 76 97 65 49
第5遍排序:13 27 38 49 49 97 65 76
第6遍排序:13 27 38 49 49 65 97 76
第7遍排序:13 27 38 49 49 65 76 97
空间效率:O(1)
时间效率:O(n2)
稳定性:不稳定
(2)堆排序
堆的定义:n个关键字序列L[1…n]称为堆
①L(i)>=L(2i)且L(i)>=L(2i+1) 或者
②L(i)<=L(2i)且L(i)<=L(2i+1)
满足①称为大顶堆;满足②称为小顶堆
建立大顶堆的算法
template <class T>
void HeadAdjust(T* arry, int k, int len)
{
int temp = arry[k];
for (int i = k * 2; i <= len; i = i * 2)
{
if (i < len && arry[i] < arry[i + 1])//比较k节点的两个子节点
{
i++; //取较大的一个子节点下标
}
if (temp > arry[i]) //k节点即根节点,比它的孩子节点的值都大,不用调整
break;
else
{
arry[k] = arry[i]; //调整值大的节点为k节点
k = i;
}
}
arry[k] = temp;
}
template<class T>
void BulidMaxHeap(T* arry, int len)
{
for (int i = len/2; i >=1; i--)
{
HeadAdjust(arry, i, len);
}
}
堆排序算法
template<class T>
void HeapSort(T* arry, int len)
{
BulidMaxHeap(arry, len);
for (int i = len; i > 1; i--)
{
swap(arry[i], arry[1]);
HeadAdjust(arry, 1, i - 1);
}
}
输入:
int arry[9] = { 0,53,17,78,9,45,65,87,32};
大顶堆的输出,从下标1开始,下标0忽略
87 45 78 32 17 65 53 9
78 45 65 32 17 9 53
65 45 53 32 17 9
53 45 9 32 17
45 32 9 17
32 17 9
17 9
9
空间效率:空间复杂度O(1)
时间效率:建堆时间O(n),堆排序时间复杂度O(nlog2n)
稳定性:不稳定
五、归并排序
(1)归并排序
归并是将两个或者两个以上的有序表合并成一个有序表。
下面描述2路归并算法:
template<class T>
void Merge(T* arry, int low, int mid, int high)
{
T* temp=new T[high+1]; //辅助数组
for (int i = low; i <= high; i++)
{
temp[i] = arry[i]; //把arry数组元素全部复制到temp中
}
int i,j,k;
for ( i = low, j=mid+1, k=i; i<=mid&&j<=high; k++)//比较左右两个半边数组元素大小,依次放入到arry数组中去
{
if (temp[i] < temp[j])
{
arry[k] = temp[i++];
}
else
{
arry[k] = temp[j++];
}
}
while (i<=mid) //把左半边没有放进去的元素放进arry中
{
arry[k++] = temp[i++];
}
while (j<=high)//把右半边没有放进去的元素放进arry中
{
arry[k++] = temp[j++];
}
}
template<class T>
void MergeSort(T* arry, int low, int high)
{
if (low < high)
{
int mid = (low + high) / 2; //划分
MergeSort(arry, low, mid); //对左侧递归
MergeSort(arry, mid + 1, high); //对右侧递归
Merge(arry, low, mid, high); //合并
}
}
空间效率:需要n个辅组单元,O(n)
时间效率:O(nlog2n)
稳定性:稳定