- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
书接上回,今天和大家一起动手来自己实现树.
相信通过前面的章节学习,大家已经明白树是什么了,今天我们主要针对二叉树,分别使用顺序存储和链式存储来实现树.
我们在上一节中说过,如果从树的顶层开始从上到下,从左到右,对每个节点按顺序进行编号,根节点为1作为起始,则有节点编号i,其左子节点编号为2i,其右子节点编号为2i+1。但是我们知道数组的索引是从0开始,因此如果我们用数组实现二叉树,那么我们可以把上面的编号改为从0开始,因此可以得到如下结论:
节点编号:i; 其左子节点编号:2i+1; 其右子节点编号:2i+2;
初始化主要是指定树节点容量,创建指定容量的数组.
//初始化树为指定容量 public MyselfTreeArray Init(int capacity) { //初始化指定长度数组用于存放树节点元素 _array = new T[capacity]; //返回树 return this; }
树节点数可以通过内部数组长度直接获取.
//树节点数量 public int Count { get { return _array.Length; } }
获取节点索引主要是为了把节点值转为索引值,因为我们是使用数组实现,元素的操作更多依赖索引。其实我们还有其他方案来处理获取索引的方式,比如可以通过设计元素对象使其包含索引字段,这样其实操作起来更为方便。下面我们还是用最直接获取的方法作为演示.
//返回指定数据的索引 public int GetIndex(T data) { if (data == null) { return -1; } //根据值查找索引 return Array.IndexOf(_array, data); }
该方法用于实现通过子节点索引计算父节点索引,无论左子节点还是右子节点,使用下面一个公式就可以了,代码如下:
//根据子索引计算父节点索引 public int CalcParentIndex(int childIndex) { //应用公式计算父节点索引 var parentIndex = (childIndex + 1) / 2 - 1; //检查索引是否越界 if (childIndex <= 0 || childIndex >= _array.Length || parentIndex < 0 || parentIndex >= _array.Length) { return -1; } //返回父节点索引 return parentIndex; }
该方法用于根据父节点索引计算左子节点索引.
//根据父节点索引计算左子节点索引 public int CalcLeftChildIndex(int parentIndex) { //应用公式计算左子节点索引 var leftChildIndex = 2 * parentIndex + 1; //检查索引是否越界 if (leftChildIndex <= 0 || leftChildIndex >= _array.Length || parentIndex < 0 || parentIndex >= _array.Length) { return -1; } //返回左子节点索引 return leftChildIndex; }
该方法用于根据父节点索引计算右子节点索引.
//根据父节点索引计算右子节点索引 public int CalcRightChildIndex(int parentIndex) { //应用公式计算右子节点索引 var rightChildIndex = 2 * parentIndex + 2; //检查索引是否越界 if (rightChildIndex <= 0 || rightChildIndex >= _array.Length || parentIndex < 0 || parentIndex >= _array.Length) { return -1; } //返回右子节点索引 return rightChildIndex; }
该方法通过节点索引获取节点值,如果索引越界则报错.
//通过节点索引获取节点值 public T Get(int index) { //检查索引是否越界 if (index < 0 || index >= _array.Length) { throw new IndexOutOfRangeException("无效索引"); } //返回节点值 return _array[index]; }
该方法是通过节点索引获取其左节点值,首先获取左子节点索引,再取左子节点值.
//通过节点索引获取其左子节点值 public T GetLeftChild(int parentIndex) { //获取左子节点索引 var leftChildIndex = CalcLeftChildIndex(parentIndex); //检查索引是否越界 if (leftChildIndex < 0) { throw new IndexOutOfRangeException("无效索引"); } //返回左子节点值 return _array[leftChildIndex]; }
该方法是通过节点索引获取其右节点值,首先获取右子节点索引,再取右子节点值.
//通过节点索引获取其右子节点值 public T GetRightChild(int parentIndex) { //获取右子节点索引 var rightChildIndex = CalcRightChildIndex(parentIndex); //检查索引是否越界 if (rightChildIndex < 0) { throw new IndexOutOfRangeException("无效索引"); } //返回右子节点值 return _array[rightChildIndex]; }
该方法是通过节点索引添加或更新节点值,因为数组初始化的时候已经有了默认值,因此添加或者更新节点值就是直接给数组元素赋值,如果索引越界则报错.
//通过节点索引添加或更新节点值 public void AddOrUpdate(int index, T data) { //检查索引是否越界 if (index < 0 || index >= _array.Length) { throw new IndexOutOfRangeException("无效索引"); } //更新值 _array[index] = data; }
该方法根据节点值添加或更新其左子节点值,首先通过节点值找到其索引,然后通过其索引计算出其左子节点索引,索引校验成功后,直接更新左子节点值.
//通过节点值添加或更新左子节点值 public void AddOrUpdateLeftChild(T parent, T left) { //获取节点索引 var parentIndex = GetIndex(parent); //获取左子节点索引 var leftChildIndex = CalcLeftChildIndex(parentIndex); //检查索引是否越界 if (leftChildIndex < 0) { throw new IndexOutOfRangeException("无效索引"); } //更新值 _array[leftChildIndex] = left; }
该方法根据节点值添加或更新其右子节点值,首先通过节点值找到其索引,然后通过其索引计算出其右子节点索引,索引校验成功后,直接更新右子节点值.
//通过节点值添加或更新右子节点值 public void AddOrUpdateRightChild(T parent, T right) { //获取节点索引 var parentIndex = GetIndex(parent); //获取左子节点索引 var rightChildIndex = CalcRightChildIndex(parentIndex); //检查索引是否越界 if (rightChildIndex < 0) { throw new IndexOutOfRangeException("无效索引"); } //更新值 _array[rightChildIndex] = right; }
该方法通过节点索引删除其自身以及其所有后代节点,删除后代节点需要左右子节点分别递归调用方法自身.
//通过节点索引删除节点及其所有后代节点 public void Remove(int index) { //非法索引直接跳过 if (index < 0 || index >= _array.Length) { return; } //清除节点值 _array[index] = default; //获取左子节点索引 var leftChildIndex = CalcLeftChildIndex(index); //递归删除左子节点及其所有后代 Remove(leftChildIndex); //获取右子节点索引 var rightChildIndex = CalcRightChildIndex(index); //递归删除右子节点及其所有后代 Remove(rightChildIndex); }
该方法通过节点值删除其左节点以及其左节点所有后代节点,首先通过节点值获取节点索引,然后计算出左子节点索引,最后通过调用删除节点Remove方法完成删除.
//通过节点值删除其左节点及其所有后代节点 public void RemoveLeftChild(T parent) { //获取节点索引 var parentIndex = GetIndex(parent); //获取左子节点索引 var leftChildIndex = CalcLeftChildIndex(parentIndex); //检查索引是否越界 if (leftChildIndex < 0) { throw new IndexOutOfRangeException("无效索引"); } //删除左子节点及其所有后代 Remove(leftChildIndex); }
该方法通过节点值删除其右节点以及其右节点所有后代节点,首先通过节点值获取节点索引,然后计算出右子节点索引,最后通过调用删除节点Remove方法完成删除.
//通过节点值删除其右节点及其所有后代节点 public void RemoveRightChild(T parent) { //获取节点索引 var parentIndex = GetIndex(parent); //获取右子节点索引 var rightChildIndex = CalcRightChildIndex(parentIndex); //检查索引是否越界 if (rightChildIndex < 0) { throw new IndexOutOfRangeException("无效索引"); } //删除右子节点及其所有后代 Remove(rightChildIndex); }
前序遍历的核心思想就是先打印树的根节点,然后再打印树的左子树,最后打印树的右子树.
//前序遍历 public void PreOrderTraversal(int index = 0) { //非法索引直接跳过 if (index < 0 || index >= _array.Length) { return; } //打印 Console.Write(_array[index]); //获取左子节点索引 var leftChildIndex = CalcLeftChildIndex(index); //打印左子树 PreOrderTraversal(leftChildIndex); //获取右子节点索引 var rightChildIndex = CalcRightChildIndex(index); //打印右子树 PreOrderTraversal(rightChildIndex); }
中序遍历的核心思想就是先打印树的左子树,然后再打印树的根节点,最后打印树的右子树.
//中序遍历 public void InOrderTraversal(int index = 0) { //非法索引直接跳过 if (index < 0 || index >= _array.Length) { return; } //获取左子节点索引 var leftChildIndex = CalcLeftChildIndex(index); //打印左子树 InOrderTraversal(leftChildIndex); //打印 Console.Write(_array[index]); //获取右子节点索引 var rightChildIndex = CalcRightChildIndex(index); //打印右子树 InOrderTraversal(rightChildIndex); }
后序遍历的核心思想就是先打印树的左子树,然后再打印树的右子树,最后打印树的根节点.
//后序遍历 public void PostOrderTraversal(int index = 0) { //非法索引直接跳过 if (index < 0 || index >= _array.Length) { return; } //获取左子节点索引 var leftChildIndex = CalcLeftChildIndex(index); //打印左子树 PostOrderTraversal(leftChildIndex); //获取右子节点索引 var rightChildIndex = CalcRightChildIndex(index); //打印右子树 PostOrderTraversal(rightChildIndex); //打印 Console.Write(_array[index]); }
层次遍历的核心思想是借助队列,从根节点开始,从上到下,从左到右,先入列后等待后续打印,然后出列后立即打印,同时把此节点的左右子节点按顺序加入队列,循环往复直至所有元素打印完成.
//层次遍历 public void LevelOrderTraversal() { //创建一个队列用于层次遍历 var queue = new Queue(); //先把根节点索引0入队 queue.Enqueue(0); //只有队列中有值就一直处理 while (queue.Count > 0) { //出列,取出第一个节点索引 var currentIndex = queue.Dequeue(); //打印第一个节点值 Console.Write(_array[currentIndex]); //获取左子节点索引 int leftChildIndex = CalcLeftChildIndex(currentIndex); // 如果左子节点存在,将其索引加入队列 if (leftChildIndex >= 0) { queue.Enqueue(leftChildIndex); } //获取右子节点索引 int rightChildIndex = CalcRightChildIndex(currentIndex); // 如果右子节点存在,将其索引加入队列 if (rightChildIndex >= 0) { queue.Enqueue(rightChildIndex); } } }
链表实现通用性会更强,基本可以实现所有的树结构,同时操作也更方便了,下面我们还是以二叉树的实现为例做演示.
在初始化树根节点前需要定义好链表的节点,其包含数据域和左右子节点,同时在树种还需要维护根节点以及节点数量两个私有字段.
public class MyselfTreeNode { //数据域 public T Data { get; set; } ////左子节点 public MyselfTreeNode Left { get; set; } //右子节点 public MyselfTreeNode Right { get; set; } public MyselfTreeNode(T data) { Data = data; Left = null; Right = null; } } public class MyselfTreeLinkedList { //根节点 private MyselfTreeNode _root; //树节点数量 private int _count; //初始化树根节点 public MyselfTreeLinkedList InitRoot(T root) { _root = new MyselfTreeNode(root); //树节点数量加1 _count++; //返回树 return this; } }
树节点数量可以通过私有字段_count直接返回.
//获取树节点数量 public int Count { get { return _count; } }
根节点可以通过私有字段_root直接返回.
//获取根节点 public MyselfTreeNode Root { get { return _root; } }
该方法是通过节点添加或更新其左子节点值.
//通过指定节点添加或更新左子节点值 public void AddOrUpdateLeftChild(MyselfTreeNode parent, T left) { if (parent.Left != null) { //更节点值 parent.Left.Data = left; return; } //添加节点 parent.Left = new MyselfTreeNode(left); //节点数量加1 _count++; }
该方法是通过节点添加或更新其右子节点值.
//通过指定节点添加或更新右子节点元素 public void AddOrUpdateRightChild(MyselfTreeNode parent, T right) { if (parent.Right != null) { //更节点值 parent.Right.Data = right; return; } //添加节点 parent.Right = new MyselfTreeNode(right); //节点数量加1 _count++; }
该方法通过节点删除其自身以及其所有后代节点,需要递归删除左右子节点及其所有后代节点.
//通过指定节点删除节点及其后代节点 public void Remove(MyselfTreeNode node) { if (node.Left != null) { //递归删除左子节点的所有后代 Remove(node.Left); } if (node.Right != null) { //递归删除右子节点的所有后代 Remove(node.Right); } //删除节点 node.Data = default; //节点数量减1 _count--; }
核心思想和数组实现一样.
//前序遍历 public void PreOrderTraversal(MyselfTreeNode node) { //打印 Console.Write(node.Data); if (node.Left != null) { //打印左子树 PreOrderTraversal(node.Left); } if (node.Right != null) { //打印右子树 PreOrderTraversal(node.Right); } }
核心思想和数组实现一样.
//中序遍历 public void InOrderTraversal(MyselfTreeNode node) { if (node.Left != null) { //打印左子树 InOrderTraversal(node.Left); } //打印 Console.Write(node.Data); if (node.Right != null) { //打印右子树 InOrderTraversal(node.Right); } }
核心思想和数组实现一样.
//后序遍历 public void PostOrderTraversal(MyselfTreeNode node) { if (node.Left != null) { //打印左子树 PostOrderTraversal(node.Left); } if (node.Right != null) { //打印右子树 PostOrderTraversal(node.Right); } //打印 Console.Write(node.Data); }
核心思想和数组实现一样.
//层次遍历 public void LevelOrderTraversal() { //创建一个队列用于层次遍历 Queue<>> queue = new Queue<>>(); //先把根节点入队 queue.Enqueue(_root); //只有队列中有值就一直处理 while (queue.Count > 0) { //出列,取出第一个节点 var node = queue.Dequeue(); //打印第一个节点值 Console.Write(node.Data); // 如果左子节点存在将其加入队列 if (node.Left != null) { queue.Enqueue(node.Left); } // 如果右子节点存在将其加入队列 if (node.Right != null) { queue.Enqueue(node.Right); } } }
注:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Planner 。
最后此篇关于数据结构-树,三探之代码实现的文章就讲到这里了,如果你想了解更多关于数据结构-树,三探之代码实现的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
Hiện tại tôi đang cố gắng xây dựng một từ điển dựa trên bảng băm. Logic là: có một cấu trúc được gọi là HashTable, chứa các nội dung sau: HashFunc HashFunc; PrintFunc PrintEntry; CompareF
Nếu tôi có một con trỏ tới một cấu trúc/đối tượng và cấu trúc/đối tượng đó chứa hai con trỏ khác tới các đối tượng khác và tôi muốn xóa "đối tượng chứa hai con trỏ đó mà không phá hủy các con trỏ mà nó giữ" - tôi phải làm thế nào? Con trỏ đến đối tượng A (bao gồm con trỏ đến đối tượng
Mã như thế này package main import "fmt" type Hello struct { ID int Raw string } type World []*Hell
Tôi có một tệp CSV theo định dạng sau: Mô-đun, Chủ đề, Chủ đề phụ. Tệp này cần có thể được nhập vào cơ sở dữ liệu MySQL theo định dạng sau: CREATE TABLE `modules` (`id
Thông thường tôi sử dụng lệnh như copy((uint8_t*)&POD, (uint8_t*)(&POD + 1 ), back_inserter(rawData)); copy((uint8_t*)&PODV
Lỗi: UNION chỉ có thể thực hiện trên các bảng có kiểu cột tương thích. struct(lớp: chuỗi, số_hướng_lên: chuỗi, điểm_hướng_lên: chuỗi)<> struct(số_hướng_lên: chuỗi, lớp: chuỗi
Tôi có một mảng các con trỏ tới các cấu trúc và tôi đang cố gắng thực hiện một vòng lặp while sử dụng chúng. Tôi không hoàn toàn tự tin về cách khởi tạo chính xác, nhưng tôi luôn làm thế này: Entry *newEntry = malloc(sizeof(Entry)
Tôi đang học C và câu hỏi của tôi có thể hơi ngớ ngẩn nhưng tôi thấy bối rối. Trong một hàm như thế này: int afunction(somevariables) { if (someconditions)
Hiện tại tôi đang làm bài tập lập trình và tôi không thực sự hiểu hết về liên kết vì chúng ta chưa đề cập đến nó. Nhưng tôi cảm thấy mình cần nó để làm những gì tôi muốn làm, vì một mảng là không đủ nên tôi đã tạo một cấu trúc như sau struct node { float coef;
Cho đoạn mã sau: #include #include #define MAX_SIZE 15 typedef struct{ int touchdowns; int intercepti
struct contact list[3]; int checknullarray() { for(int x=0;x<10;x++) { if(strlen(con
Câu hỏi này đã có câu trả lời ở đây: Đã đóng cách đây 11 năm. Bản sao có thể: Vòng lặp "for" trống trong Facebook ajax AJAX gọi cái gì
Tôi vừa duyệt một tệp trong Reflector và thấy điều này trong một hàm tạo struct: this = new Binder.SyntaxNodeOrToken(); Tôi chưa từng thấy thuật ngữ đó trước đây. Có ai có thể giải thích cách bài tập này hoạt động trong C# không?
Tôi thường sử dụng hằng chuỗi, như: DICT_KEY1 = 'DICT_KEY1' DICT_KEY2 = 'DICT_KEY2' ... và nhiều khi tôi không quan tâm văn bản thực tế là gì, miễn là chúng là duy nhất và con người có thể đọc được.
Tôi mới học C và tôi không hiểu tại sao đoạn mã sau không hoạt động: typedef struct{ uint8_t a; uint8_t* b; } test_struct; test_struct
Bạn có thể tạo một cấu trúc hoạt động giống như một trong các lớp tích hợp sẵn, nơi bạn có thể gán giá trị trực tiếp mà không cần gọi thuộc tính không? Tiền thân: RoundedDouble đếm; đếm = 5; Thay vì sử dụng RoundedDouble đếm;
Đây là mã của tôi: #include typedef struct { const char *description; float value; int age; } swag
Khi tạo danh sách lồng nhau, tôi nghĩ R có cấu trúc đặt tên hữu ích cho các phần tử danh sách. Tôi có một danh sách các danh sách và muốn áp dụng một hàm cho mỗi vectơ có trong bất kỳ danh sách nào. lapply thực hiện điều này nhưng sau đó loại bỏ cấu trúc đặt tên của danh sách. Làm thế nào để tôi chồng các cột lồng nhau
Tôi đang tạo một công cụ tổ chức cá nhân cho mục đích học tập và tôi chưa từng làm việc với XML trước đây nên tôi không chắc giải pháp của mình có phải là tốt nhất hay không. Đây là cấu trúc cơ bản của tệp XML mà tôi đính kèm:
Tôi mới làm quen với các khái niệm nosql nên khi bắt đầu học PouchDB, tôi đã tìm thấy bảng chuyển đổi này. Điều tôi băn khoăn là, PouchDB hoạt động như thế nào nếu tôi có nhiều bảng, điều đó có nghĩa là tôi cần phải tạo nhiều cơ sở dữ liệu không? Bởi vì theo những gì tôi tìm thấy trong pouchdb
Tôi là một lập trình viên xuất sắc, rất giỏi!