数据结构之二元查找树转有序双向链表详解与示例(C/C++)

文章目录

    • 1. 二元查找树(BST)简介
    • 2. 有序双向链表(DLL)简介
    • 3. 二元查找树的实现
    • 4. 转换为有序双向链表的步骤
    • 5. C++实现代码
    • 6. C实现代码
    • 7. 效率与空间复杂度比较
    • 8. 结论

在这里插入图片描述


在数据结构与算法中,树和链表都是非常重要的数据结构。二元查找树(Binary Search Tree,简称BST)是一种特殊的二叉树,它具有很好的查找性能。而双向链表(Doubly Linked List)则是一种线性结构,它允许我们在两个方向上遍历。本文将详细介绍如何将一个二元查找树转换为有序双向链表。

1. 二元查找树(BST)简介

二元查找树是一种数据结构,其中每个节点最多有两个子节点:左子树节点值小于当前节点,右子树节点值大于当前节点。这种性质使得BST能够支持高效的查找操作。

2. 有序双向链表(DLL)简介

有序双向链表是一种链表结构,除了具有链表节点的基本特征外,它还具有指向前一个节点的指针,这使得链表节点可以双向访问。此外,链表节点按照节点值的顺序排列,形成有序链表。

3. 二元查找树的实现

首先定义二元查找树的节点结构体:

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

class BinarySearchTree {
public:
    BinarySearchTree() : root(nullptr) {}

    void insert(int val) {
        root = insertIntoTree(root, val);
    }

    TreeNode* insertIntoTree(TreeNode* node, int val) {
        if (node == nullptr) return new TreeNode(val);
        if (val < node->val) {
            node->left = insertIntoTree(node->left, val);
        } else {
            node->right = insertIntoTree(node->right, val);
        }
        return node;
    }

    // 这里可以添加更多的操作,例如查找、删除等...
};

4. 转换为有序双向链表的步骤

转换过程如下:

  1. 从二元查找树的根节点开始,进行中序遍历(左-根-右)。
  2. 遍历过程中,将节点转换为链表节点,并维护前一个节点和后一个节点的指针关系。
  3. 遍历完成后,头节点的前驱节点指向nullptr,最后一个节点的后继节点也指向nullptr,形成一个闭合的双向链表。

5. C++实现代码

下面是一个C++示例,展示如何实现二元查找树到有序双向链表的转换:

class Node {
public:
    int val;
    Node *prev;
    Node *next;
    Node(int x) : val(x), prev(nullptr), next(nullptr) {}
};

class BinarySearchTreeToDoublyLinkedList {
public:
    Node* convert(TreeNode* root) {
        if (root == nullptr) return nullptr;
        
        Node* head = nullptr, *prev = nullptr;
        convertTreeToList(root, &head, &prev);
        
        // 闭合双向链表
        if (prev != nullptr) {
            prev->next = head;
            head->prev = prev;
        }
        return head;
    }

    void convertTreeToList(TreeNode* node, Node** headRef, Node** prevRef) {
        if (node == nullptr) return;
        
        // 左子树转换
        convertTreeToList(node->left, headRef, prevRef);
        
        // 处理当前节点
        Node* newNode = new Node(node->val);
        if (*headRef == nullptr) {
            *headRef = newNode;
        } else {
            (*prevRef)->next = newNode;
            newNode->prev = *prevRef;
        }
        *prevRef = newNode;
        
        // 右子树转换
        convertTreeToList(node->right, headRef, prevRef);
    }
};

示例

假设我们有以下二元查找树:

    4
   / \
  2   5
 / \
1   3

通过上述代码转换后,我们得到以下有序双向链表:

1 <-> 2 <-> 3 <-> 4 <-> 5

6. C实现代码

以下是一个完整的C语言示例,展示了如何将二元查找树转换为有序双向链表:

#include <stdio.h>
#include <stdlib.h>

// 二元查找树节点定义
typedef struct Node {
    int data;
    struct Node* left;
    struct Node* right;
} Node;

// 双向链表节点定义
typedef struct DListNode {
    int data;
    struct DListNode* prev;
    struct DListNode* next;
} DListNode;

// 创建双向链表节点
DListNode* createDListNode(int data) {
    DListNode* newNode = (DListNode*)malloc(sizeof(DListNode));
    newNode->data = data;
    newNode->prev = newNode->next = NULL;
    return newNode;
}

// 插入节点
Node* insert(Node* root, int data) {
    if (root == NULL) {
        return createNode(data);
    }
    if (data < root->data) {
        root->left = insert(root->left, data);
    } else if (data > root->data) {
        root->right = insert(root->right, data);
    }
    return root;
}

// 查找节点
Node* search(Node* root, int data) {
    if (root == NULL || root->data == data) {
        return root;
    }
    if (data < root->data) {
        return search(root->left, data);
    }
    return search(root->right, data);
}

// 将二元查找树转换为双向链表
DListNode* bstToDList(Node* root) {
    if (root == NULL) {
        return NULL;
    }
    
    DListNode* head = NULL;
    DListNode* tail = NULL;
    
    // 递归遍历树
    if (root->left != NULL) {
        head = bstToDList(root->left);
    }
    if (head != NULL) {
        head->next = createDListNode(root->data);
        head->next->prev = head;
    } else {
        head = createDListNode(root->data);
    }
    
    if (root->right != NULL) {
        tail = bstToDList(root->right);
    }
    if (tail != NULL) {
        tail->prev = head;
        head->next = tail;
    } else {
        tail = head;
    }
    
    return head;
}

// 打印双向链表
void printDList(DListNode* head) {
    DListNode* current = head;
    while (current != NULL) {
        printf("%d ", current->data);
        current = current->next;
    }
    printf("\n");
}

int main() {
    Node* root = NULL;
    root = insert(root, 50);
    insert(root, 30);
    insert(root, 70);
    insert(root, 20);
    insert(root, 40);
    insert(root, 60);
    insert(root, 80);

    printf("BST elements: ");
    Node* temp = root;
    while (temp != NULL) {
        printf("%d ", temp->data);
        temp = temp->right;
    }
    printf("\n");

    DListNode* dlist = bstToDList(root);
    printf("Doubly linked list elements: ");
    printDList(dlist);

    return 0;
}

7. 效率与空间复杂度比较

二元查找树:

  1. 插入、删除和查找的时间复杂度平均为O(log n),在最坏的情况下(树退化为链表)为O(n)。
  2. 空间复杂度为O(n),用于存储树中的节点。

有序双向链表:

  1. 插入和删除操作在O(1)时间复杂度内完成,因为每个节点都包含前驱和后继指针。
  2. 查找操作的时间复杂度为O(n),因为可能需要遍历整个链表。
  3. 空间复杂度同样为O(n),用于存储链表中的节点。

在实际应用中,二元查找树在搜索效率方面具有优势,特别是在数据量较大且分布均匀时。然而,当树高度不平衡时,其性能会显著下降。另一方面,有序双向链表在插入和删除操作上具有更好的性能保证,但查找操作效率较低。

8. 结论

二元查找树和有序双向链表各有优缺点,适用于不同的场景。二元查找树更适合需要频繁搜索的场景,而有序双向链表则在插入和删除操作更频繁的场景中表现更好。选择哪种数据结构取决于具体应用的需求和性能考量。

在实现时,二元查找树的结构简单,逻辑清晰,而有序双向链表需要额外的指针来维护前后关系,这可能会增加代码的复杂性。然而,有序双向链表的一个优点是它不会像二元查找树那样出现平衡问题,这使得它在某些应用中更加稳定。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/843345.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】进程间通信之-- 共享内存与信号量的介绍(下)

前言 上一篇&#xff0c;我们由进程间通信&#xff0c;引入并讲述了管道、匿名管道和命名管道&#xff0c;本节&#xff0c;将继续学习进程间通信的另一种方式之&#xff0c;共享内存。还要学习几个系统调用接口&#xff0c;并演示两个进程通过共享内存来进行通信。。。 目录 1…

WGS84经纬度坐标 GCJ02火星坐标 BD09百度坐标互相转换

WGS84经纬度坐标 GCJ02火星坐标 BD09百度坐标互相转换 背景&#xff1a;uniapp做的微信小程序&#xff0c;使用到了相机拍照并获取位置坐标信息&#xff1b;在腾讯地图上展示坐标点位置信息&#xff1b; 由于业务需要我们的PC端用的不是腾讯地图&#xff0c;需要使用WGS84坐标或…

Java二十三种设计模式-代理模式模式(8/23)

代理模式&#xff1a;为对象访问提供灵活的控制 引言 代理模式&#xff08;Proxy Pattern&#xff09;是一种结构型设计模式&#xff0c;它为其他对象提供一个代替或占位符&#xff0c;以控制对它的访问。 基础知识&#xff0c;java设计模式总体来说设计模式分为三大类&#…

【网络】socket和udp协议

socket 一、六个背景知识1、Q1&#xff1a;在进行网络通信时&#xff0c;是不是两台机器在进行通信&#xff1f;2、端口号3、端口号vs进程PID4、目的端口怎么跟客户端绑定的呢&#xff1f;也就是怎么通过目的端口去找到对应的进程的呢&#xff1f;5、我们的客户端&#xff0c;怎…

uniapp小程序上传pdf文件

<template><view class"mainInnBox"><view class"formBox"><!-- 注意&#xff0c;如果需要兼容微信小程序&#xff0c;最好通过setRules方法设置rules规则 --><u-form :model"form" ref"uForm" :rules&quo…

Window版本nginx修改文件访问句柄数被限制,解决大并发量访问时无法响应问题

目录 一、问题背景 二、问题分析 三、解决办法 一、问题背景 Windows版本因为文件访问句柄数被限制为1024了&#xff0c;当大并发量访问时就会无法响应。会有如下错误提示&#xff1a;maximum number of descriptors supported by select() is 1024 while connecting to ups…

C# 基础语法(一篇包学会的)

C#&#xff08;读作"C Sharp"&#xff09;是一种现代的、通用的面向对象编程语言&#xff0c;由微软公司开发。它结合了C和C的强大特性&#xff0c;并去掉了一些复杂性&#xff0c;使得开发者可以更加高效地编写代码。 一、入坑C# (一) 安装和设置 首先&#xff0c…

【LeetCode】从中序与后序遍历序列构造二叉树

目录 一、题目二、解法完整代码 一、题目 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7], …

鸿蒙OpenHarmony Native API【Rawfile】

Rawfile Overview Description: 提供操作rawfile目录和rawfile文件的功能 提供操作rawfile目录和rawfile文件功能 功能包括遍历、打开、搜索、读取和关闭rawfile Since: 8 Version: 1.0 Summary Files File NameDescription[raw_dir.h]提供rawfile目录相关功能[raw…

Debian Linux下rclone挂载谷歌云盘碰到的坑

可能是明月好久没有使用境外服务器挂载境外的云盘缘故吧,今天一个代维客户需要他的Linux服务器挂载谷歌云盘好进行云备份,本来是个很简单的事儿,没想到在rclone连接谷歌云盘的时候卡壳了,可是把明月给难为坏了,搜索到的简体中文教程倒是很多,但没有一个提到这个“坑”,最…

Docker启动PostgreSql并设置时间与主机同步

在 Docker 中启动 PostgreSql 时&#xff0c;需要配置容器的时间与主机同步。可以通过在 Dockerfile 或者 Docker Compose 文件中设置容器的时区&#xff0c;或者使用宿主机的时间来同步容器的时间。这样可以确保容器中的 PostgreSql 与主机的时间保持一致&#xff0c;避免在使…

C语言数组的相关案例

引导案例&#xff1a; 数组的遍历&#xff1a;这里需要注意的是我们在遍历数组时是使用for循环&#xff0c;这里则需要计算数组的长度 计算公式&#xff1a;sizeof(数组名) / sizeof(数组的数据类型) #include<stdio.h> int main() {int arr[] { 1,2,3,4,5,6,7,8 ,9,…

大厂面试-基本功

大厂面试第4季 服务可用性多少个9是什么意思遍历集合add或remove操作bughashcode冲突案例BigdecimalList去重复IDEA Debugger测试框架ThreaLocal父子线程数据同步 InheritableThreadLocal完美解决线程数据同步方案 TransmittableThreadLocal 服务可用性多少个9是什么意思 遍历集…

增值税发票核验API在Java、Python、PHP中的使用教程

在企业经营中&#xff0c;发票扮演着记录交易、报销和纳税的重要角色。然而&#xff0c;由于发票的众多类型和复杂的制作方式&#xff0c;一些企业可能面临着假发票、冒充发票等风险。为了提高财务管理的效率和准确性&#xff0c;以及防范不法行为&#xff0c;增值税发票核验成…

定制QCustomPlot 带有ListView的QCustomPlot 全网唯一份

定制QCustomPlot 带有ListView的QCustomPlot 文章目录 定制QCustomPlot 带有ListView的QCustomPlot摘要需求描述实现关键字: Qt、 QCustomPlot、 魔改、 定制、 控件 摘要 先上效果,是你想要的,再看下面的分解,顺便点赞搜藏一下;不是直接右上角。 QCustomPlot是一款…

Jenkins-zookeeper-docker-xxljob-rancher

文章目录 Jenkins实战1 新建任务需要的配置pipeline Zookeeper基础 Docker基础实操windows11 docker mysql DockerhouseDockerhubxxl-Job基础实战 Rancher基础思考 实战1 Rancher的某个namespace的scale为0 Jenkins 实战 1 新建任务需要的配置pipeline 该代码是Jenkinsfile&…

【接口自动化_08课_Pytest+Yaml+Allure框架】

上节课一些内容 的补充 1、openxl这个方法&#xff0c;第一个元素是从1开始的&#xff0c;不是从0开始 回写的列在程序里写的是11&#xff0c;是因为是固定值 一、1. Yaml入门及应用 1、什么是yaml YAML&#xff08;/ˈjməl/&#xff0c;尾音类似camel骆驼&#xff09;是一…

探索Python日志管理的优雅之道:Loguru库入门指南

探索Python日志管理的优雅之道&#xff1a;Loguru库入门指南 背景&#xff1a;为何选择Loguru&#xff1f; 在Python开发过程中&#xff0c;日志记录是不可或缺的一部分&#xff0c;它帮助我们追踪程序的运行状态&#xff0c;调试程序错误&#xff0c;并记录关键信息。然而&am…

【Linux】-----权限详解

目录 一、Linux下的权限概念 Ⅰ、是什么&#xff1f; Ⅱ、Linux下的两种角色 角色 如何添加普通用户 身份的转化方式 身份的提权 添加普通用户至白名单 二、Linux下的权限管理 Ⅰ、文件访问者的分类(Linux下的“人”) Ⅱ、文件类型和访问权限(事物属性) 1.文件类型 …