01 (顺序查找)数组 A[]中有 n 个整数,没有次序,数组从下标 1 开始存储,请写出顺序查找任一元素 k 的算法,若查找成功,则返回元素在数组中的位置; 若查找不成功,则返回 0。
int search(int a[], int n, int k) {
for (int i = 1; i <= n; i++) {
if (a[i] == k) {
return i;
}
}
return 0;
}
02 (折半查找)已知一个顺序表 L,其中的元素递增有序排列,请分别写出折半查找的递归算法和非递归算法,查找 L 中值为 key 的元素位置。
非递归版本
int BinarySearch(SqList L, int key) {
int low = 0, high = L.length - 1;
int mid;
while (low <= high) {
mid = (low + high) / 2;
if (L.data[mid] == key) {
return mid;
}
//变换low和high
else if (L.data[mid] > key) {//在左边
high = mid - 1;
}
else {
low = mid + 1;
}
}
return -1;
}
递归版本
int BinarySearch(SqList L, int key, int low, int high) {
int mid = (low + high) / 2;
if (low > high) {//递归边界
return -1;
}
if (L.data[mid] == key) {
return mid;
}
else if (L.data[mid] > key) {
high = mid - 1;
//BinarySearch(L, key, low, high);需要return回来,不然程序不会实现递归功能
return BinarySearch(L, key, low, high);
}
else {
low = mid + 1;
return BinarySearch(L, key, low, high);
}
}
03 线性表中各结点的检索概率不等时,可用如下策略提高顺序检索的效率:若找到指定的结点,则将该结点和其前驱结点(若存在)交换,使得经常被检索的结点尽量位于表的前端。试设计在顺序结构和链式结构的线性表上实现上述策略的顺序检索算法。
顺序结构
void exchange(SqList& L, int k) {
if (L.data[0] == k) {
return;
}
int temp;
for (int i = 1; i < L.length; i++) {
if (L.data[i] == k) {
temp = L.data[i - 1];
L.data[i - 1] = L.data[i];
L.data[i] = temp;
return;
}
}
}
顺序表可以直接定义一个temp变量进行交换
链式结构
- 这链式结构看着简单,但是涉及到三个指针的变化,需要理清楚;
void LinkListSearch(LinkList& L, int k) {//带头结点的单链表 L
if (L->next->data == k)//第一个数据结点无前驱结点,若其为查找结点则返回
return;
LNode* p = L;//定义遍历指针 p
while (p->next->next) {//若 p->next->next 所指的结点不为空则继续循环
if (p->next->next->data == k) {//p->next->next 所指结点为查找结点则交换
LNode* q = p->next, * r = q->next;//定义两个指针辅助进行交换
q->next = r->next;//交换所找结点与其前驱结点位置
p->next = r;
r->next = q;
return;
}
p = p->next;//若此时 p->next->next 所指结点不是查找结点则继续遍历
}
}
这里是p指针指向头结点,判断其后一个的后一个结点是否等于k
void exchange(LinkList& L, int k) {//这里是带头结点的单链表
if (L->next->data == k) {//第一个数据结点无前驱结点,若其为查找结点则返回
return;
}
LNode* pre = L;
LNode* p = pre->next;
while (p->next != NULL) {
if (p->next->data == k) {
LNode* q = p->next;
p->next = q->next;
q->next = p;
pre->next = q;
return;
}
pre = p;//继续后移查找
p = pre->next;
}
}
这是笔者自己写的,定义pre指向头结点,p指向pre的后一个结点
04 请分别写出在二叉排序树中查找数据域值为 k 结点位置的递归算法和非递归算法。
- 二叉排序树,就是左子树<根<右子树
- 这里查找位置,因此函数类型是结点类型,找不到就返回NULL;
非递归
//非递归
BiTNode* BST_search(BiTree T, int k) {
while (T != NULL) {
if (T->data == k) {
return T;
}
else if (T->data > k) {//去左子树查找
T = T->lchild;
}
else {//去右子树找
T = T->rchild;
}
}
return NULL;
}
递归
BiTNode* BST_search(BiTree T, int k) {
if (T == NULL) {
return NULL;
}
if (T->data == k) {
return T;
}
else if (k < T->data) {//查找的值小于根节点的值,说明在左子树
return BST_search(T->lchild, k);
}
else {
return BST_search(T->rchild, k);
}
}
这里递归调用时候别忘了写return
05 请写出在一棵二叉排序树 T 中插入值为 k 结点的算法,插入成功则返回1,插入失败则返回 0,然后写出根据数组 A 中存储元素值顺序构造一棵二叉排序树的算法,假设数组 A 的长度为 n。
- 二叉排序树不存在值相同的结点
非递归插入结点
//非递归插入结点
int BST_insert(BiTree& T, int k) {
if (T == NULL) {
BiTNode* p = (BiTNode*)malloc(sizeof(BiTNode));
p->data = k;
p->lchild = NULL;
p->rchild = NULL;
T = p;
return 1;
}
while (T != NULL) {
if (T->data == k) {
return 0;
}
else if (k < T->data) {//左子树去插入
if (T->lchild == NULL) {//如果左子树没有结点,说明找到插入位置了
BiTNode* p = (BiTNode*)malloc(sizeof(BiTNode));
p->data = k;
p->lchild = NULL;
p->rchild = NULL;
T->lchild = p;
return 1;
}
else {//如果左子树有结点,继续遍历左子树
T = T->lchild;
}
}
else {//去右子树插入k
if (T->rchild == NULL) {
BiTNode* p = (BiTNode*)malloc(sizeof(BiTNode));
p->data = k;
p->lchild = NULL;
p->rchild = NULL;
T->rchild = p;
return 1;
}
else {
T = T->rchild;
}
}
}
}
递归插入结点
//递归插入结点
int BST_insert(BiTree& T, int k) {
if (T == NULL) {//找到插入位置了
T = (BiTNode*)malloc(sizeof(BiTNode));
T->data = k;
T->lchild = NULL;
T->rchild = NULL;
return 1;
}
if (T->data == k) {
return 0;
}
else if (k < T->data) {
return BST_insert(T->lchild, k);
}
else {
return BST_insert(T->rchild, k);
}
}
构造二叉排序树
void Create_BST(BiTree& T, int A[], int n) {
T = NULL;//第一次调用 BST_Insert 函数前需将 T 初始化为空才可正常调用
int i = 0;//定义遍历索引 i
while (i < n) {//遍历数组 A
BST_insert(T, A[i]);//对数组 A 中每个元素进行二叉排序树的插入操作
i++;
}
}
06 设计一个算法,求出给定二叉排序树中最小和最大的关键字。
int Min(BiTree T) {
while (T -> lchild != NULL) {
T = T->lchild;
}
return T->data;
}
int Max(BiTree T) {
while (T->rchild != NULL) {
T = T->rchild;
}
return T->data;
}
07 试编写一个算法,求出指定结点在给定二叉排序树中的层次。
- 给定结点在二叉排序树中的层次,不需要考虑其他找不到的情况,只需要判断结点是否和根结点相同还是在左子树还是右子树;
int depth(BiTree T, BiTNode*p) {
int n = 1;定义变量 n 用来记录 T 指针指向结点所在层次
while (p != T) {
if (p->data > T->data) {
T = T->rchild;
}
else {
T = T->lchild;
}
n++;//返回查找结点所在层次
}
return n;
}
08 设计一个算法,从大到小输出二叉排序树中所有值不小于 k 的关键字。
- 从大到小,原先二叉排序树是左<根<右,如果对其中序遍历,就是从小到大排序;
- 由于中序遍历是左根右,如果变成右根左,就是从大到小排序了;
- 因此这里修改中序递归遍历即可,将中序遍历顺序改为右子树、根、左子树
void print_k(BiTree T, int k) {
if (T != NULL) {
print_k(T->rchild, k);
if (T->data >= k) {
printf("%d", T->data);
}
print_k(T->lchild, k);
}
}
if (T->data >= k) 不是T->rchild>=k,是因为这个if是在处理根了,中序遍历,中间的代码是处理根结点的;
对递归不太了解的,也可以用下面的,逻辑更清楚一些
void print_k(BiTree T, int k) {
if (T == NULL) {
return;
}
else{
print_k(T->rchild, k);
if (T->data >= k) {
printf("%d", T->data);
}
print_k(T->lchild, k);
}
}
09 试编写一个算法,判断给定的二叉树是否是二叉排序树。
代码的写法与之前写的不太相同,需要好好理解理解
- 判断是否为二叉排序树,就是判断中序遍历是否严格递增有序;
- 可以定义一个前驱pre变量,将其设置为int的最小值,然后通过比较,返回最后结果;
int pre = -32767;//int变量的最小值,将其作为前驱,
int Is_BST(BiTree T,int &pre) {
int b1, b2;//用于记录左右子树是否为二叉排序树,是则返回 1,不是则返回 0
if (T == NULL) {
return 1;//空树是二叉排序树
}
else {
b1 = Is_BST(T->lchild, pre);//用b1接收左子树情况
if (b1 == 0 || T->data <= pre) {
return 0;//若左子树返回 0 或根结点小于等于左子树最大结点则不是
}
pre = T->data;//递归判断右子树是否为二叉排序树
b2 = Is_BST(T->rchild, pre);//返回右子树判断结果
return b2;
}
}
这里T==NULL return 1在叶子结点的左右孩子那都会返回1,导致叶子结点以下只需要看pre和根结点的大小,
这里结尾是return b2
10 试编写一个判断二叉排序树是否是平衡二叉树的算法。
- 平衡二叉树,左右子树高度只差为-1、0、1
- 使用递归,需要:左子树是平衡二叉树,右子树是平衡二叉树,左右子树高度差(平衡因子)应该是-1或0或1,这样才是平衡二叉树,平衡因子求的是子树的高度,因此这题重点考察求树高度
int hight(BiTree T) {
int left, right;
if (T == NULL) {//空树高度为0
return 0;
}
left = hight(T->lchild);
right = hight(T->rchild);
if (left == -1 || right == -1 || abs(left - right) > 1) {左子树或右子树不是 AVL 或左右子树高度差大于 1 则返回-1,说明不是平衡二叉树
return -1;
}
else {//是平衡二叉树
return max(left, right) + 1;
}
}
int judgeAVL(BiTree T) {
if (hight(T) == -1) {
return 0;
}
else {
return 1;
}
}
这里hight函数用来求平衡二叉树的高度,但是经过改写,即如果左子树或右子树不是平衡二叉树,返回-1,左右子树高度差大于 1(不是平衡二叉树)也返回-1,如果是平衡二叉树,需要返回包括根结点在内的左右子树的最大高度,后序递归需要继续比较。
int abs(int a) {//求绝对值,用于计算平衡因子
if (a >= 0) {
return a;
}
else {
return -a;
}
}
int max(int x,int y) {//求两数之中最大值
if (x >= y) {
return x;
}
else {
return y;
}
}
11 在平衡二叉树的每个结点中增设一个域 lsize,存储以该结点为根的左子树中的结点个数加一。编写一个算法,确定树中第 k 小结点的位置。
- 平衡二叉树是特殊的二叉排序树,所以中序排序是递增的序列,第k小的位置就是中序遍历(左根右)第k个位置,
- lsize就是左子树的节点数量加上根节点的数量,变相可以用lsize来帮助确定第k小的位置,
- 对于右子树,k需要减去根节点的lsize(在右子树重新开始找k-lsize的位置)
BiTNode* Search_k(BiTree T, int k) {
if (k < 1 || T == NULL) {
return NULL;
}
else if (T->lsize == k) {
return T;
}
else if (T->lsize > k) {//往左子树找
return Search_k(T->lchild, k);
}
else {
return Search_k(T->rchild, k - T->lsize);
}
}
12 编写一个递归算法,在一棵有 n 个结点的、随机建立起来的二叉排序树上查找第 k 小的元素,并返回指向该结点的指针变量。要求算法的平均时间复杂度为 O(log2n)。二叉排序树的每个结点中除 data,lchild,rchild 等数据成员外,增加一个 count 成员,保存以该结点为根的子树中结点个数。
- 还是找中序遍历第k个,确定根可以用T->lchild->count判断,只要加1就是根的位置,
- 这里需要注意,如果没有左子树,就无法像上面一样判断了;而且,如果没有左子树,且要找的k不是1,说明要去右子树查找,需要递归,k-1表示排除根节点;
BiTNode* Search(BiTree T, int k) {
if (k<1 || k>T->count) {
return NULL;
}
if (T->lchild == NULL) {//没有左子树
if (k == 1) {
return T;
}
else {//不是第一个结点,说明去右子树找,需要递归
return Search(T->rchild, k - 1);//若不是第一小,则递归的去右子树中找第 k-1 小的结点
}
}
else{//有左子树
if (T->lchild->count == k - 1) {//若左子树有 k-1 个结点,则根为第 k 小结点
return T;
}
else if (T->lchild->count > k - 1) {//在左子树去查找
return Search(T->lchild, k);
}
else {
return Search(T->rchild, k - (T->lchild->count + 1));//T->lchild->count + 1表示左子树+根结点数量
}
}
}
下面是自己写错的,没有考虑没有左子树的情况;
BiTNode* Search(BiTree T, int k) {
if (k<1 || k>T->count) {
return NULL;
}
if (T->lchild->count == k - 1) {
return T;
}
else if (T->lchild->count > k - 1) {//在左子树去查找
return Search(T->lchild, k);
}
else {
return Search(T->rchild, k - (T->lchild->count + 1));
}
}
13 (直接插入排序)请分别写出顺序存储和链式存储的直接插入排序算法。
直接插入排序将要排序的元素划分为两部分,一部分是排好序的部分,另一部分是待排序的部分,然后从待排序部分中依次原则元素与排好序元素进行比较,找到插入位置进行插入,然后去处理下一个,直到全部元素均处理完,
顺序存储
- 这里顺序存储第一个位置(下标为0)处不存放元素;
- 每次需要排序时,先将待排序元素放在下标为A[0]处,然后用A[0]的值与前面的值进行比较,笔者之前这里是用A[j] < A[j - 1]
void InsertSort(int A[], int n) {//数组从下标 1 开始存储数据
if (n == 1) {
return;}
int i, j;
for (i = 2; i <= n; i++) {
if (A[i] >= A[i - 1]);
else {//后一个元素要比前一个元素小,需要寻找插入位置
A[0] = A[i];//将待排序元素复制到 A[0]
j = i;
while (A[0] < A[j - 1]) {//while循环寻找待排序元素插入位置
A[j] = A[j - 1];//元素后移,为待排序元素插入做准备
j--;
}
A[j] = A[0];//找到插入位置后,将待排序元素插入
}
}
}
void InsertSort(int A[], int n) {//数组从下标 1 开始存储数据
int i, j;
for (i = 2; i <= n; i++) {//遍历数组
if (A[i] < A[i - 1]) {//若遍历元素小于前一个元素则需寻找其插入位置
A[0] = A[i];//将待排序元素复制到 A[0]
for (j = i - 1; A[0] < A[j]; j--)//寻找待排序元素插入位置
A[j + 1] = A[j];//元素后移,为待排序元素插入做准备
A[j + 1] = A[0];//找到插入位置后,将待排序元素插入
}
}
}
下面是笔者第一次写的代码,感觉怪怪的,而且代码运行会有错误,就是因为没有使用A[0]和A[j-1]比较。
void InsertSort(int A[], int n) {//数组从下标 1 开始存储数据
if (n == 1) {
return;}
int i, j;
for (i = 2; i <= n; i++) {
if (A[i] >= A[i - 1]);
else {
A[0] = A[i];
j = i;
while (A[j] < A[j - 1]) {
A[j] = A[j - 1];
j--;
}
A[j] = A[0];
}
}
}
链式存储
- 也是分为排好序部分和待排序部分,然后找插入位置,但是链表不能从后往前查找,只能从前往后查找;
- 一开始直接将链表分为两部分,由于链式存储的性质,需要四个指针变量,两个指向排好序部分最后两个结点,两个指向待排序部分的最开始两个结点,方便后续指针域的改变;
- 由于从左到右找,需要找左边结点值大于右边第一个待排序结点的值,且之前设置已排序结点的最后一个结点的指针为空,因此需要判断是否为空(别忘记);
- 找到插入位置后,右边待排序需要指针r记录位置,防断链
//链式存储
void InsertSort(LinkList& L) {
if (L->next == NULL) {//若链表为空则直接结束
return;
}
LNode* pre, * q, * p, * r;//定义 pre 和 q 遍历已排序结点,p 和 r 遍历待排序结点
p = L->next->next;//让 p 指针指向第二个数据结点,即待排序的第一个结点
L->next->next = NULL;//断开链表,使 L 变成只有一个结点的有序链表
while (p != NULL) {//遍历待排序结点
pre = L;//初始化 pre 和 q 两个指针
q = pre->next;
while (q != NULL && q->data < p->data) {//寻找待排序结点插入位置,如果q结点指针域不为空,且其值小于待排序第一个结点的值,说明需要继续找插入位置
pre = q;
q = q->next;
}
//跳出循环后,说明q为空了或者左边的值大于等于右边的值,可以插入了,在pre和q之间进行插入
r = p->next;//记录下一个待排序结点位置,放置断链
p->next = q;//将 p 指针指向的待排序结点插入到有序链表中
pre->next = p;
p = r;//更新 p 指针位置,使其指向下一个待排序结点
}
}
14 有一个数组 A 存储着 m+n 个整型元素,元素从下标 1 开始存储,其前m 个元素递增有序,后 n 个元素递增有序,请设计一个算法使这个数组整体有序。
- 就是考察直接插入排序,已知前m个有序
void InsertSort(int A[], int m, int n) {//运用直接插入排序
int i, j;
for (i = m + 1; i <= m + n; i++) {//变量 i 负责遍历待排序元素
if (A[i] < A[i - 1]) {//若遍历元素小于前一个元素则需寻找其插入位置
A[0] = A[i];//将此遍历元素复制到 A[0]
for (j = i - 1; A[0] < A[j]; j--){//寻找遍历元素插入位置
A[j + 1] = A[j];//元素后移,为遍历元素插入做准备
}
A[j + 1] = A[0];//找到插入位置后,将待排序元素插入
}
}
}
15 (折半插入排序)请写出折半插入排序算法。
- 折半插入排序通过两个索引,low和high,判断mid;
- 插入到low的位置,也就是high+1的位置,
- 定义i, j, low, high, mid变量,i记录待排序的元素,j在元素后移的时候使用;
- 折半查找也是从第二个元素开始查找,
void InsertSort(int A[], int n) {//数组中元素从下标 1 开始存储
int i, j, low, high, mid;
for (i = 2; i <= n; i++) {
//先将元素放在A[0]中,防止后序丢失
A[0] = A[i];
low = 1;//low 索引记录已排序的第一个元素位置
high = i - 1;//high 索引记录已排序的最后一个元素位置
while (low <= high) {
mid = (low + high) / 2;//mid会根据high的变化而变化,因此需要放在循环里面
if (A[mid] < A[0]) {//如果要插入元素比中间元素大,要去右边
low = mid + 1;
}
else {
high = mid - 1;
}
}
//low>high说明找到要插入位置了,为low指向的位置,因此需要给他腾出空间插入
//这里j的位置可以自己推一下
for (j = i - 1; j >= low; j--) {
A[j + 1] = A[j];
}
//在low的位置插入元素
A[low] = A[0];
}
}
16 (希尔排序)请写出希尔排序算法。
- 先去追求数组的局部有序,然后逼近全局有序,
- 步长d,对子表直接使用直接插入排序
- 步长初始为数组元素数量的一半,然后每次变为原来的1/2,遍历各个子表,下一个元素位置要加d,如1,1+d,1+2d;2,2+d,2+2d
- 理解不了d的,将代码中的d换成1,就和之前的直接插入排序没啥区别了
void ShellSort(int A[], int n) {//数组中元素从下标 1 开始存储
int d, i, j;//定义变量 d 记录增量
for (d = n / 2; d >= 1; d = d / 2)//初始时增量为 n/2,之后每次增量更新为上一趟的一半
for (i = 1 + d; i <= n; i++)//遍历各个子表,下一个元素是d+1,如1,1+d,1+2d;2,2+d,2+2d
if (A[i] < A[i - d]) {//若此子表待排序元素小于上一个元素则需插入排序
A[0] = A[i];//将待排序元素复制到 A[0]
for (j = i - d; j > 0 && A[0] < A[j]; j = j - d)//寻找待排序元素插入位置
A[j + d] = A[j];//比待排序元素大的元素后移
A[j + d] = A[0];//插入待排序元素
}
}
17 (冒泡排序)请写出冒泡排序算法。
- 冒泡是两两比较,交换,每趟冒泡排序会确定一个元素的位置,
- n个元素,确定前n-1个元素位置,就有序了;
- 冒泡结束条件还有一个,当有一次冒泡排序整个数组根本没有数据交换,说明也可以结束了,因此需要定义一个flag
- i, j, temp,flag;i表示n个元素最多只需要排序n-1次,j每次排序从后往前冒泡的,temp是交换元素顺序,flag 用来记录每次冒泡过程中有无元素交换
void BubbleSort(int A[], int n) {//数组中元素从下标 0 开始存储
int i, j, temp;
int flag;//定义 flag 用来记录每次冒泡过程中有无元素交换
for (i = 0; i < n - 1; i++) {//n 个元素进行冒泡排序最多进行 n-1 次冒泡,或者说每一次循环确定A[i]的元素,假如5个元素,A0-A4,所以i<n-1;
flag = 0;//初始化 flag 标志
for (j = n - 1; j > i; j--) {//遍历待排序的元素进行冒泡,假如i=0时,j=4,3,2,1,需要进行4次j的变化,最后j为1,所以判断条件为j
if (A[j - 1] > A[j]) {//若两元素出现逆序则需交换
temp = A[j];
A[j] = A[j - 1];
A[j - 1] = temp;
flag = 1;//执行交换操作后需将 flag 标志置为 1
}
}
if (flag == 0)//若某一次冒泡过程中无交换,则证明已经有序直接结束
return;
}
}
18 编写双向冒泡排序算法,在正反两个方向交替进行扫描,即第一趟把关键字最大的元素放在序列的最后面,第二趟把关键字最小的元素放在序列的最前面,如此反复进行。
- 双向冒泡,定义两个索引,
- low如果不在high的左边了,说明可以结束了
- 一次for循环确定一个元素的位置,因此low和high的变化是在for循环结束后进行更改;
- 这里在while循环中设置flag==1进入循环也可以和上题一样,单独使用if进行判断,因为只要发生元素移动,flag变成1就能进入下次循环;
void BubbleSort(int A[], int n) {
int low = 0, high = n - 1;//定义两个索引分别记录待排序元素的低地址和高地址
int temp, flag = 1;//定义 flag 变量记录冒泡排序过程中有无元素交换,将flag设置为1,是为了进入while循环
while (low < high&& flag == 1) {
flag = 0;//初始时让 flag 等于 0 代表目前还没有元素交换
for (int i = low; i < high; i++) {//从前往后冒泡
if (A[i] > A[i + 1]) {//若出现逆序则交换两个元素
temp = A[i];
A[i] = A[i + 1];
A[i + 1] = temp;
flag = 1;//执行完交换操作后将 flag 变量改为 1
}
}
high--;//更新待排序元素的高地址
for (int i = high; i > low; i--) {//从后往前冒泡
if (A[i] < A[i - 1]) {//若出现逆序则交换两个元素
temp = A[i];
A[i] = A[i - 1];
A[i - 1] = temp;
flag = 1;//执行完交换操作后将 flag 变量改为 1
}
}
low++;//更新待排序元素的低地址
}
}
19 (快速排序)请写出快速排序算法。
- 重点:划分,先去找基准,让其先到最终的位置,然后这一次划分,其左边元素都比其小,右边元素都比其大,因此基准到了最终的位置;
//划分函数,返回基准(已确定元素)的下标
int Partition(int A[], int low, int high) {
int pivot = A[low];//定义一个基准,初始时赋值为 low 位置的元素
while (low < high) {//low 小于 high 才有意义
while (low < high && A[high] >= pivot) {//右边的大于或者等于基准,右边high左移
high--;//从右往左遍历寻找小于基准的元素
}
//跳出while循环,说明找到小于基准的元素,则互换将其放到左边
A[low] = A[high];
while (low < high && A[low] <= pivot) {//左边的小于于或者等于基准,左边low左移
low++;//若左往右遍历寻找大于基准的元素
}
//跳出while循环,说明找到大于基准的元素,则互换将其放到右边
A[high] = A[low];
}
A[low] = pivot;//把基准放入其最终位置
return low;//返回本次划分所确定元素的位置
}
void QuickSort(int A[], int low, int high) {
if (low < high) {//low 小于 high 才有意义
int pivotpos = Partition(A, low, high);//划分数组,并返回确定元素位置,返回下标,
QuickSort(A, low, pivotpos - 1);//递归划分左边待排序元素
QuickSort(A, pivotpos + 1, high);//递归划分右边待排序元素
}
}
20 有一个整型数组 A 存储了 n 个元素,其元素序列是无序的,元素从下标为 1 开始存储,请设计一个算法让数组 A 中的最后一个元素放在数组 A 整体排序后的最终位置上,结果返回其数组下标,要求关键字的比较次数不超过 n。
- 可以看出写快速排序,这里是基准是最后一个元素,由于题目已经给出基准了,不需要像上题要单独写递归函数
int Partition(int A[], int n) {//改写快速排序的划分算法
int low = 1, high = n;//定义并初始化高低索引
int pivot = A[high];//定义一个基准,初始时赋值为最后一个元素
while (low < high) {//low 小于 high 才有意义
while (low < high && A[low] <= pivot)
low++;//从左往右遍历寻找大于基准的元素
A[high] = A[low];//找到大于基准的元素后,则将其放到右边
while (low < high && A[high] >= pivot)
high--;//从右往左遍历寻找小于基准的元素
A[low] = A[high];//找到小于基准的元素后,则将其放到左边
}
A[low] = pivot;//把基准放入其最终位置
return low;//返回本次划分所确定元素的位置
}
21 请设计一个尽可能高效的算法,使之能够在数组 A[1…n]中找出第 k 小的元素(即从小到大排序后处于第 k 个位置的元素)。
- 从小到大第k个元素,使用快速排序的划分加递归能实现;
- 需要将最开始的low和high保存,递归的时候需要用到,没有的话,如果low和high变化后,就无法找到最开始的low和high了;
- 如果一次快排就找到,直接返回就行,一次找不到,需要递归寻找
int Search_k(int A[], int low, int high, int k) {
int pivot = A[low];
int low_temp = low, high_temp = high;//定义并初始化高低索引
while (low < high) {
while (low < high && A[high] >= pivot) {
high--;
}
A[low] = A[high];
while (low < high && A[low] <= pivot) {
low++;//从左往右遍历寻找大于基准的元素
}
A[high] = A[low];
}
A[low] = pivot;
//接下来进行递归
if (low == k) {//若此次基准所在的最终位置就是 k 则直接返回此基准的值
return A[low];
}
else if (low > k) {//这次划分的基准在k的右边,说明要去左边找
return Search_k(A, low_temp, low - 1, k);
}
else {
return Search_k(A, low + 1, high_temp, k);
}
}
22 已知一个数组 A 存储了 n 个元素,且数组中每个元素都是不相同的正整数,请设计一个高效算法把数组 A 中所有奇数移动到所有偶数前边的算法。
- 定义两个索引low和high,low索引从左往右找偶数,high从右往左找奇数,找到互换;
- 最后交换后别忘了low++和high–
void Move(int A[], int n) {
int low = 0, high = n - 1;
int temp;//用于交换
while (low < high) {
while (low < high && A[low] % 2 != 0) {//如果左边遍历的是奇数,继续往后遍历
low++;
}
while (low < high && A[high] % 2 != 1) {//如果右边遍历的是奇数,继续往前遍历
high--;
}
if (low < high) {//如果这时候两个没在一起,说明左右都找到元素了,交换
temp = A[low];
A[low] = A[high];
A[high] = temp;
low++;
high--;
}
}
}
23 顺序放置 n 个球,每个球的颜色是红,白,蓝之一,要求重新排列这些球,使得所有红色的球在前,白色球居中,蓝色的球在后。假设放置球的序列存放在数组 A 中,0 代表球为红色,1 代表球为白色,2 代表球为蓝色,请设计一个算法为这 n 个球排序。
- 荷兰🚩问题,定义low、high、i(遍历索引),i从左到右遍历,会遇到三种情况,如下,
遇到红色:0,红色需要在左边,swap(i,low) ;low++;i++;
遇到白色:1,本来就在中间,i++;
遇到蓝色:2,蓝色需要在右边,swap(i,high);high–;但是这里i不能++;因为从high处换过来的可能是个0,需要继续比较。
- while循环的判断条件是i<=high;
void Swap(int& a, int& b) {//两变量交换的辅助函数
int temp;
temp = a;
a = b;
b = temp;
}
void Sort(int A[], int n) {
int low = 0, high = n - 1, i = 0;//low 左面全是红色,high 右面全是蓝色
while (i <= high) {//遍历数组
if (A[i] == 0) {//若遍历元素为红色,则与 low 所在位置交换
Swap(A[i], A[low]);
low++;//更新 low 索引位置
i++;//继续遍历
}
else if (A[i] == 1) {//若遍历元素为白色,则继续遍历
i++;
}
else {//若遍历元素为蓝色,则与 high 所在位置交换
Swap(A[i], A[high]);
high--;//更新 high 索引位置,注意不能继续遍历
}
}
}
24 (简单选择排序)请分别写出顺序结构存储和链式结构存储的简单选择排序算法。
顺序存储
- i此次选择排序是在确定哪个元素的位置,j负责后续元素,min记录当前遍历过的最小值的位置,这里i、j、min都是下标,temp就是交换变量;
- 选择排序,数组n个下标n-1,我们只需要从0确定到n-2个元素就好了,也就是最后一个元素不需要确定;
//顺序存储
void SelectSort(int A[], int n) {
int i, j, min, temp;
for (i = 0; i < n - 1; i++) {//i 为此次排序需确定的位置
min = i;//min 记录已遍历过的最小元素位置
for (j = i + 1; j < n; j++)//遍历剩余待排序元素
if (A[j] < A[min])//若发现有元素比当前记录最小值小则更新 min
min = j;
temp = A[i];//遍历结束后将选择出的最小值交换到地址 i 的位置
A[i] = A[min];
A[min] = temp;
}
}
链式存储
- 可以去找最大元素,然后头插法,这样就形成了递增有序;
- 找最大值,需要四个指针,p遍历指针、p指针的前驱指针pre、maxpre指针,maxp记录目前最大值的指针,maxpre目前最大值的前驱;
- 取出结点进行头插后,如何进行第二次?如何找到待排序的第一个结点?定义一个新的指针r负责记录已经排好序的最后一个结点,第一次找到最大值后,将其插入到头结点后,r指向他,此时r已经指向最大值了,而其后的结点均为未排序的结点,所以只需要一次循环就能实现找到待排序的第一个结点;
- 初始化的时候得根据r来确定指针指向;
//链式存储
void SelectSort(LinkList& L) {
LNode* pre, * p, * maxpre, * maxp;
LNode* r = L;
while (r->next != NULL) {//已经排好序的最后一个结点后面如果没有结点,说明已经有序了,结束
//初始化
pre = r;
p = r->next;
maxpre = pre;
maxp = p;
while (p != NULL) {
if (p->data > maxp->data) {
maxpre = pre;
maxp = p;
}
//无论是否大于,都得往后走
pre = p;
p = p->next;
}
//运行一次while循环说明找到最大值了,需要将其取出来
maxpre->next = maxp->next;
//头插
maxp->next = L->next;
L->next = maxp;
//第一次进行寻找最大值的时候,r指向L,此时需要将r指向maxp,如果不是第一次寻找最大值,r已经指向最大值了,
//也就是排好序结点的最后一个,已经不需要变化了
if (r == L) {
r = maxp;
}
}
}
25 (计数排序)有一种简单的排序算法称为计数排序,其算法思想是选取一个待排序的元素值,然后遍历整个数组,统计数组中有多少个元素比选取的待排序元素值小,假设统计出的计数值为 c,则可以根据计数值 c 在新的有序数组中找到待排序元素的最终位置。现有一个数组 A,存放了 n 个互不相同的整型元素,请使用计数排序算法为数组 A 排序,排序结果存放在数组 B 中。
- i表示当前待排序元素的下标,j表示遍历整个数组,找有几个元素比待排序元素小,count用来计数;
- 这里的j每次也是从第一个元素开始找的,我一开始是从i+1处开始找的;
void Count(int A[], int B[], int n) {
int i, j;
int count;
for (i = 0; i < n; i++) {//下标为 i 的元素为此次待排序元素
count = 0;
for (j = 0; j < n; j++) {
if (A[j] < A[i]) :
count++;//统计数组 A 中比待排序元素小的元素个数
}
B[count] = A[i];//将待排序元素复制到数组 B 的最终排序位置上
}
}
26 (堆排序)有一个数组 A 存储了 n 个整型元素,元素从数组下标 1 开始存储,请写出对数组 A 使用堆排序的算法。
- 大根堆,根大于等于左孩子和右孩子,小根堆,根小于等于左孩子和右孩子
//调整元素
void HeapAdjust(int A[], int n, int k) {//参数 k 表示此次调整的元素所在位置
A[0] = A[k];//将调整元素赋值给 A[0]
int i = 2 * k;//定义索引 i 初始化为调整元素左孩子所在位置
while (i <= n) {//若 i 所在位置存在元素,则进入循环
//让i处在较大元素的下标处
if (i<n && A[i + 1]>A[i]) {//比较调整位置的左、右孩子大小,让i到相应位置,
i++;//若右孩子大,则让索引 i 更新为调整位置右孩子所在位置
}
if (A[i] > A[0]) {//比较调整元素与值较大孩子大小
A[k] = A[i];//若孩子的值大于调整元素,则让孩子来到调整位置
k = i;//将较大孩子所在位置作为下一次循环的调整位置
i = 2 * i;//更新索引 i 为下一次循环调整位置的左孩子索引
}
else//若索引 i 超出了堆的存储范围,则直接跳出循环
break;
}
A[k] = A[0];//将调整元素放入其最终位置
}
//构造大根堆
void BuildMaxHeap(int A[], int n) {//建立初始大根堆
for (int i = n / 2; i > 0; i--)//从最后一个分支结点(i=n/2)开始逐个往前遍历进行调整
HeapAdjust(A, n, i);
}
//堆排序
void HeapSort(int A[], int n) {
BuildMaxHeap(A, n);//建立初始大根堆
int temp;
for (int i = n; i > 1; i--) {//i 为待排序的最后一个元素地址
temp = A[1];//将大根堆的根与待排序的最后一个元素交换位置
A[1] = A[i];
A[i] = temp;
HeapAdjust(A, i - 1, 1);//重新调整大根堆,待排序元素个数-1
}
}
27 有一个数组 A 存储了 n 个整型元素,元素从数组下标 1 开始存储,试设计一个算法判断数组 A 是否构成一个小根堆。
- 将数组变成完全二叉树,方便观察;
- 判断是否为小根堆,就是判断分支节点是否小于等于左右孩子;
- 首先需要判断结点个数是奇数还是偶数,会对最后一个分支结点有影响,如果总共n个元素,n若为奇数,导致最后一个分支节点只有一个左孩子,特殊考虑,n为偶数,则可以正常处理即可;
int JudgeMinHeap(int A[], int n) {
if (n % 2 == 0) {//若堆中有偶数个元素,则最后一个分支结点只有左孩子
if (A[n / 2] > A[n])//判断最后一个单分支结点是否满足小根堆性质
return 0;
//是n/2-1
for (int i = n / 2 - 1; i > 0; i--)//判断其它双分支结点是否满足小根堆性质
if (A[i] > A[2 * i] || A[i] > A[2 * i + 1])
return 0;
}
else {//若堆中有奇数个元素,则最后一个分支结点有左、右孩子
for (int i = n / 2; i > 0; i--)//判断所有双分支结点是否满足小根堆性质
if (A[i] > A[2 * i] || A[i] > A[2 * i + 1])
return 0;
}
return 1;//若全部分支结点都满足小根堆性质,则该数据序列是小根堆
}
28 (二路归并排序)请写出二路归并排序算法。
- 归并操作,需要辅助的数组,复制原来的数组内容,然后使用归并排序
void Merge(int A[], int low, int mid, int high) {//两个有序序列归并
//左右两个序列的元素个数
int n1 = mid - low + 1, n2 = high - mid;
//定义两个数组
int* L = (int*)malloc(sizeof(int) * n1);//定义辅助数组 L
int* R = (int*)malloc(sizeof(int) * n2);//定义辅助数组 R
int i, j, k;//i是遍历左边的数组,j是遍历右边的数组,k是遍历原始数组
for (i = 0, k = low; k <= mid; i++, k++)//将 A 数组中左边的有序序列复制到 L
L[i] = A[k];
for (j = 0, k = mid + 1; k <= high; j++, k++)//将 A 数组中右边的有序序列复制到 R
R[j] = A[k];
for (i = 0, j = 0, k = low; i < n1 && j < n2; k++) {//遍历 L 和 R 两数组进行归并
if (L[i] <= R[j])//若 L 中元素更小或相等,则将 L 中遍历元素放回数组 A
A[k] = L[i++];
else//若 R 中元素更小,则将 R 中遍历元素放回数组 A
A[k] = R[j++];
}
//可能还剩下一个数组没有遍历完
while (i < n1)//若遍历结束后,数组 L 有剩余,则将剩余元素放回数组 A
A[k++] = L[i++];
while (j < n2)//若遍历结束后,数组 R 有剩余,则将剩余元素放回数组 A
A[k++] = R[j++];
}
//需要多次归并,需要递归
void MergeSort(int 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);//将两个有序序列归并
}
}