前言
今年下半年都在忙着准备408初试,写代码机会很少,记录算法算是一种消遣,希望可以帮助到正在学习算法的你啦~
目录
一、快排解题
1.(14 分)有一个含有 n 个整数的无序数据序列,所有的数据元素均不相同,采用整数数组 R[0,n-1]存储, 请完成以下任务: (1)设计一个尽可能高效的算法,输出该序列中第 k(1≤k≤n)小的元素,算法中给出适 当的注释信息。 (2)分析你所设计的求解算法的平均时间复杂度,并给出求解过程。
·显然解题关键是用一种时间复杂度相对较小的排序算法(考研你的必然能想到快速排序)
快速选择算法的核心是分区(partition)过程,它将数组分为两部分,一部分元素小于或等于基准值,另一部分元素大于或等于基准值,然后递归地在包含第 k
小元素的子数组中进行选择。
//1.快速排序求解算法
int QuickSelect(int R[],int s,int t,int k) //在 R[s..t]序列中找第 k 小的元素
{ int i=s,j=t;//初始化两个指针i和j分别指向序列起始和结束位置
int temp;//用于存储基准元素
if (s<t)//如果序列长度大于1,即s和t不相等,则执行以下操作
{ temp=R[s]; //用区段的第 1 个记录作为基准
while (i!=j) //从区段两端交替向中间扫描,直至 i=j 为止
{ while (j>i && R[j]>=temp) j--; //从右向左扫描,找第 1 个小于 temp 的 R[j]
R[i]=R[j]; //将 R[j]前移到 R[i]的位置
while (i<j && R[j]<=temp) i++; //从左向右扫描,找第 1 个大于 temp 的 R[i]
R[j]=R[i]; //将 R[i]后移到 R[j]的位置
}
R[i]=temp;//将基准元素移动到正确位置
if (k-1==i) return R[i];//若基准元素恰好是第k小位置,则返回该元素
else if (k-1<i) return QuickSelect(R,s,i-1,k); //第k小元素在基准元素左侧,在左侧序列寻找
else return QuickSelect(R,i+1,t,k); //第k小元素在基准元素右侧,在右侧序列寻找
}
else if (s==t && s==k-1) //区段内只有一个元素且为 R[k-1]
return R[k-1];
}
void Mink(int R[],int n,int k) //输出整数数组 R[0..n-1]中第 k 小的元素
{ if (k>=1 && k<=n)
printf("%d\n", QuickSelect( R,0,n-1,k));
}
时间复杂度分析(快速排序时间复杂度分析):
算法过程:①分区;②递归选择;③平均情况——每次分区都能将数组大致均匀的分为两部分
设数组的大小为
n
,快速选择算法的平均时间复杂度T(n)
可以近似为:
- 每次分区操作的时间复杂度为
O(n)
,因为需要遍历整个数组来完成分区。- 递归调用处理的数组大小大约是原数组的一半。
因此,可以写出递归关系式:
T(n)=T(n/2)+O(n)T(n)=T(n/2)+O(n)
这里,
T(n/2)
表示递归调用处理较小数组的时间复杂度,O(n)
表示分区操作的时间复杂度。通过递归展开这个关系式,我们可以得到:
T(n)=T(n/2)+O(n)=T(n/4)+O(n)+O(n)=…=O(nlogn)T(n)=T(n/2)+O(n)=T(n/4)+O(n)+O(n)=…=O(nlogn)
这是因为每次递归调用,数组的大小减半,直到数组大小减少到1,这个过程大约需要
log₂n
次递归调用,每次递归调用都伴随着一个O(n)
的分区操作。
二、树之中序遍历
2.(15 分)假设以带双亲指针的二叉链表作为二叉树的存储结构,其结点结构的类型下所示: typedef char Datatype;
typedef struct node {
DataType datat;
struct node *lchild, *rchild; /*左右孩子指针*/
struct node *parent; /*指向双亲的指针*/ }
BinTNode;
typedef BinTNode *BinTree; 若 root 指向根结点,px 为指向非空二叉树中某个结点的指针,可借助该结构求得 px 所在 二叉树的中序序 列中的后继,以及 root 到 x 之间路径上的所有结点。 (1)就中序序列后继的不同情况,简要叙述实现求后继操作的方法; (2)编写算法求 px 所指结点的中序序列后继,并在算法语句中加注注释; (3)若只使用孩子指针而不使用双亲指针,编写算法输出从 root 到 px 之间路径上的结点 (说明:可使用 C 或 C++编写算法
(1)在二叉树中,一个节点的中序后继是中序遍历序列中该节点后面的第一个节点。根据节点是否有右子树,我们可以分为两种情况:
Case1:当结点px有右子树时,后继是px右子树中的最左结点,这是因为中序遍历中,先访问左子树,然后是根节点,然后是右子树,所以,右子树的最左节点是访问顺序上的下一个结点
Case2:当结点px没有右子树时,我们需要沿着px的父节点链向上寻找,直到找到一个节点,该节点是其父节点的左子节点,这个节点就是px的中序后继
BinTree InOrderSuccessor(BinTree px) {
BinTree q = px->rchild; // 尝试找到右子树的最左节点
if (q != NULL) { // 如果存在右子树
while (q->lchild != NULL) { // 沿左子树向下查找
q = q->lchild;
}
return q; // 返回找到的后继节点
} else { // 如果没有右子树,沿父节点链向上查找
BinTree p = px->parent;
while (p != NULL && px == p->rchild) { // 确保每次查找时px都是当前节点父节点的右子节点
//若px为左节点,后继必然为父节点
px = p;
p = p->parent;
}
return p; // 循环结束时p就是px的中序后继结点,
//若没有后继p将为NULL,函数也将返回NULL
}
}
// 声明一个辅助函数,用于递归地找到从 root 到 px 的路径
int FindPath(BinTree root, BinTree px, BinTNode **path, int pathLen) {
//*path指针数组,**path指向指针数组的指针
// 如果 root 为空或者 root 就是 px,那么返回 1 表示找到了路径
if (root == NULL || root == px) {
if (root == px) {
path[pathLen] = root;
return 1;
}
return 0;
}
// 将当前节点加入路径
path[pathLen] = root;
int left = FindPath(root->lchild, px, path, pathLen + 1);
if (left) return 1; // 如果在左子树中找到了路径,返回 1
int right = FindPath(root->rchild, px, path, pathLen + 1);
return right; // 如果在右子树中找到了路径,返回 1
}
// 打印从 root 到 px 的路径
void PrintPath(BinTree root, BinTree px) {
BinTNode *path[100]; // 假设路径长度不会超过 100
int pathLen = 0;
if (FindPath(root, px, path, pathLen)) {
for (int i = 0; path[i] != NULL; i++) {
printf("%c ", path[i]->data);
}
printf("\n");
} else {
printf("No path found from root to px\n");
}
}
int main() {
// 假设已经构建了二叉树并初始化了 root 和 px
BinTree root = NULL; // 这里应该是你的二叉树的根节点
BinTree px = NULL; // 这里应该是你要查找路径的目标节点
// 调用 PrintPath 函数打印路径
PrintPath(root, px);
return 0;
}