6. 排序
算法稳定性:若待排序表有两个元素
R
i
R_i
Ri? 和
R
j
R_j
Rj?,其对应的关键字相同即
k
e
y
i
=
k
e
y
j
key_i=key_j
keyi?=keyj?,且排序前
R
i
R_i
Ri? 在
R
j
R_j
Rj? 前面。
- 若排序后
R
i
R_i
Ri?? 仍然在
R
j
R_j
Rj??? 前面,则是稳定的。
- 反之亦然? 👊
6.1 直接插入排序
初始
L
[
1
]
L[1]
L[1] 可以视为已经排好的子序列,然后依次将
L
[
2
]
?
l
[
n
]
L[2]-l[n]
L[2]?l[n] 插入已经排好的序列,共执行
n
?
1
n-1
n?1 次操作。每次操作的具体步骤为从后往前依次和子序列元素比较,如果小于该元素往后移位,最后将自己插入适当的位置。
void DirectInsertSort(ElementType A[], int n)
{
int i, j;
for (i = 2; i <= n; i++)
{
if (A[i] < A[i - 1])
{
A[0] = A[i];
for (j = i - 1; A[j] > A[0]; j--)
{
A[j + 1] = A[j];
}
A[j + 1] = A[0];
}
}
}
空间复杂度:
O
(
1
)
O(1)
O(1)
时间复杂度:
- 最好情况:初始有序,每次插入元素就比较一次而不用移动元素,时间复杂度
O
(
n
)
O(n)
O(n)
- 最坏情况:初始逆序,时间复杂度
O
(
n
2
)
O(n^2)
O(n2)
- 平均时间复杂度
O
(
n
2
)
O(n^2)
O(n2)?
6.2 折半插入排序
查询元素位置用二分查找实现,但与二分查找不同,二分查找是查到元素的位置,但这里其实是通过左右比较,找到一个适当的位置,所以最后会以
h
i
g
h
=
l
o
w
?
1
high=low-1
high=low?1? 结束,所以
h
i
g
h
high
high? 指向元素就是应该插入元素的前一个元素。然后再统一向后移动元素。
void BinaryInsertSort(ElementType A[], int n)
{
int i, j, mid, low, high;
for (int i = 2; i <= n; i++)
{
A[0] = A[i];
low = 1, high = i - 1;
while (low <= high)
{
mid = (low + high) / 2;
if (A[mid] > A[0])
{
high = mid - 1;
}
else
{
low = mid + 1;
}
}
for (j = i - 1; j >= high + 1; j--)
{
A[j + 1] = A[j];
}
A[high + 1] = A[0];
}
}
空间复杂度:
O
(
1
)
O(1)
O(1)
时间复杂度:
- 仅减少了比较元素的次数,未改变移动次数,因此平均时间复杂度还是
O
(
n
2
)
O(n^2)
O(n2)?
6.3 希尔排序
将相隔某一增量的元素组成子表,对每个子表直接插入排序。这个某一增量的取值一般为
d
1
=
n
/
2
,
d
i
+
1
=
d
i
/
2
d_1=n/2,d_{i+1}=d_i/2
d1?=n/2,di+1?=di?/2,到最后一个增量为1为止。相当于在元素基本有序时,对全体进行一次直接插入排序。
void ShellSort(ElementType A[], int n)
{
for (int dk = n / 2; dk >= 1; dk = dk / 2)
{
for (int i = dk + 1; i <= n; i++)
{
if (A[i] < A[i - dk])
{
A[0] = A[i];
int j;
for (j = i - dk; A[0] < A[j] && j > 0; j -= dk)
{
A[j + dk] = A[j];
}
A[j + dk] = A[0];
}
}
}
}
空间复杂度:
O
(
1
)
O(1)
O(1)
时间复杂度:
- 分析十分复杂,记住最优情况下约为
O
(
n
1.3
)
O(n^{1.3})
O(n1.3)?,最坏情况下
O
(
n
2
)
O(n^2)
O(n2)??
6.4 冒泡排序
从后往前两两比较相邻元素的值,若为逆序则交换。这是第一趟冒泡。结果是最小的元素到了第一个位置。下一趟冒泡,已经确定的最小元素不用参与,最多重复
n
?
1
n-1
n?1? 趟冒泡就能把所有元素排好序。
void BubbleSort(ElementType A[], int n)
{
for (int i = 0; i < n - 1; ++i)
{
bool flag = false;
for (int j = n - 1; j > i; j--)
{
if (A[j - 1] > A[j])
{
swap(A[j - 1], A[j]);
flag = true;
}
}
if (!flag)
{
break;
}
}
}
空间复杂度:
O
(
1
)
O(1)
O(1)
时间复杂度:
- 最好情况:初始有序,只进行一次冒泡,时间复杂度
O
(
n
)
O(n)
O(n)
- 最坏情况:初始逆序,需要
n
?
1
n-1
n?1 次排序,时间复杂度
O
(
n
2
)
O(n^2)
O(n2)
- 平均时间复杂度
O
(
n
2
)
O(n^2)
O(n2)?
6.5 ??快速排序
任取一元素
p
i
v
o
t
pivot
pivot?? 作为枢轴,一趟排序分成2部分,
p
i
v
o
t
pivot
pivot? 左边所有元素小于它,
p
i
v
o
t
pivot
pivot?? 右边所有元素大于?它。然后分别递归的对两个子表重复上述过程。直到每部分只有一个元素。
void QuickSort(ElementType A[], int low, int high)
{
if (low < high)
{
int pivotpos = Partition(A, low, high);
QuickSort(A, low, pivotpos - 1);
QuickSort(A, pivotpos + 1, high);
}
}
int Partition(ElementType A[], int low, int high)
{
ElementType pivot = A[low];
while (low < high)
{
while (A[high] >= pivot && high > low)
{
high--;
}
A[low] = A[high];
while (A[low] <= pivot && high > low)
{
low++;
}
A[high] = A[low];
}
A[low] = pivot;
return low;
}
空间复杂度:
O
(
l
o
g
2
n
)
O(log_2n)
O(log2?n)???
时间复杂度:
O
(
l
o
g
2
n
)
O(log_2n)
O(log2?n)
6.6 简单选择排序
就每次都选择序列中最小的元素,重复
n
?
1
n-1
n?1 趟。
void SelectSort(ElementType A[], int n)
{
for (int i = 0; i < n - 1; i++)
{
int j, min = i;
for (j = i + 1; j < n; j++)
{
if (A[j] < A[min])
{
min = j;
}
}
if (min != i)
{
swap(A[i], A[min]);
}
}
}
空间复杂度:
O
(
1
)
O(1)
O(1)???
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
6.7 ??堆排序
根据堆的特性,最大堆堆顶元素就是最大值,输出堆顶元素后,将堆底元素放到堆顶,此时堆的性质被破坏,需要重新调整。
? 待解决问题?
- 1?? 如何将无序序列构造成初始堆?
- 2?? 输出堆顶元素后,如何调整堆?
对于
n
n
n 个结点的完全二叉树,最后一个结点的父节点是
n
/
2
n/2
n/2,所以从
n
/
2
n/2
n/2 为根结点的子树开始向前筛选,使子树成为堆。筛选方法:看结点是否大于左右子结点值,如小于,与左右子结点中较大者交换。交换后可能会破环下一级堆,于是继续调整。
void HeapSort(ElementType A[], int len)
{
BuildMaxHeap(A, len);
for (int i = len; i > 1; i--)
{
swap(A[i], A[1]);
HeadAdjust(A, 1, i - 1);
}
}
void BuildMaxHeap(ElementType A[], int len)
{
for (int i = len / 2; i > 0; i--)
{
HeadAdjust(A, i, len);
}
}
void HeadAdjust(ElementType A[], int k, int len)
{
A[0] = A[k];
for (int i = 2 * k; i <= len; i *= 2)
{
if (i < len && A[i] < A[i + 1])
{
i++;
}
if (A[i] <= A[0])
{
break;
}
else
{
A[k] = A[i];
k = i;
}
}
A[k] = A[0];
}
空间复杂度:
O
(
1
)
O(1)
O(1)???
时间复杂度:
O
(
n
l
o
g
2
n
)
O(nlog_2n)
O(nlog2?n)
6.8 ??归并排序
待排序表有
n
n
n?? 个记录,就看成 n 个有序的子表,每个子表长度为1,然后两两归并,得到
n
/
2
n/2
n/2 个长度为2或者1个有序表,继续两两归并,直到合并成一个长度为
n
n
n 的有序表为止。
void MergeSort(ElementType A[], int low, int high)
{
if (low < high)
{
int mid = (low + high) / 2;
MergeSort(A, low, mid);
MergeSort(A, mid + 1, high);
Merge(A, low, mid, high);
}
}
void Merge(ElementType A[], int low, int mid, int high)
{
int i = low;
int j = mid + 1;
int k;
for (int k = low; k <= high; k++)
{
B[k] = A[k];
}
for (k = low; i <= mid && j <= high; k++)
{
if (B[i] < B[j])
{
A[k] = B[i++];
}
else
{
A[k] = B[j++];
}
}
while (i <= mid)
{
A[k++] = B[i++];
}
while (j <= high)
{
A[k++] = B[j++];
}
}
空间复杂度:
O
(
n
)
O(n)
O(n)?????
时间复杂度:
O
(
n
l
o
g
2
n
)
O(nlog_2n)
O(nlog2?n)
6.9 基数排序
以关键字是1000一下的整数为例,基数
r
=
10
r=10
r=10,每个关键字由3个子关键字组成:
k
1
K
2
K
3
k^1K^2K^3
k1K2K3,分别代表百位,十位和个位,从次位开始(个位)依次进行操作。
次位优先基数排序过程:
#include <iostream>
using namespace std;
struct node
{
int data;
node *next;
};
int GetDigit(int X, int D)
{
int d, i;
for (i = 1; i <= D; i++)
{
d = X % 10;
X /= 10;
}
return d;
}
int main()
{
int radix = 10;
node *bucket[radix];
int A[10] = {278, 109, 63, 930, 589, 184, 505, 269, 8, 83};
node *list = NULL;
node *p;
for (int i = 0; i < 10; i++)
{
node *tmp = new node;
tmp->data = A[i];
tmp->next = NULL;
if (list == NULL)
{
list = tmp;
p = list;
}
else
{
p->next = tmp;
p = p->next;
}
}
for (int i = 1; i <= 3; i++)
{
for (int i = 0; i < radix; i++)
{
bucket[i] = NULL;
}
p = list;
node *tmp;
while (p)
{
tmp = p;
p = p->next;
int k = GetDigit(tmp->data, i);
if (bucket[k] == NULL)
{
bucket[k] = tmp;
}
else
{
node *q = bucket[k];
while (q->next)
{
q = q->next;
}
q->next = tmp;
}
tmp->next = NULL;
}
list = NULL;
for (int j = 0; j < radix; j++)
{
if (bucket[j] == NULL)
{
continue;
}
if (bucket[j] && list == NULL)
{
list = bucket[j];
p = list;
continue;
}
while (p->next)
{
p = p->next;
}
p->next = bucket[j];
}
cout << i << " trip: ";
p = list;
while (p)
{
cout << p->data << " ";
p = p->next;
}
cout << endl;
}
system("pause");
return 0;
}
r
r
r 为桶个数,d为子关键字个数(趟数),
n
n
n 为元素个数
空间复杂度:
O
(
r
)
O(r)
O(r)???????
时间复杂度:
O
(
d
(
n
+
r
)
)
O(d(n+r))
O(d(n+r))
6.10 ??总结
|