408算法:快排&中序遍历


前言

今年下半年都在忙着准备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(nlog⁡n)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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值