diff --git a/codes/csharp/chapter_array_and_linkedlist/LinkedList.cs b/codes/csharp/chapter_array_and_linkedlist/LinkedList.cs index 46b87a624..ecc7aabc3 100644 --- a/codes/csharp/chapter_array_and_linkedlist/LinkedList.cs +++ b/codes/csharp/chapter_array_and_linkedlist/LinkedList.cs @@ -35,7 +35,7 @@ namespace hello_algo.chapter_array_and_linkedlist /// /// 访问链表中索引为 index 的结点 /// - public static ListNode Access(ListNode head, int index) + public static ListNode? Access(ListNode head, int index) { for (int i = 0; i < index; i++) { @@ -89,8 +89,8 @@ namespace hello_algo.chapter_array_and_linkedlist Console.WriteLine($"删除结点后的链表为{n0}"); // 访问结点 - ListNode node = Access(n0, 3); - Console.WriteLine($"链表中索引 3 处的结点的值 = {node.val}"); + ListNode? node = Access(n0, 3); + Console.WriteLine($"链表中索引 3 处的结点的值 = {node?.val}"); // 查找结点 int index = Find(n0, 2); diff --git a/codes/csharp/chapter_array_and_linkedlist/list.cs b/codes/csharp/chapter_array_and_linkedlist/list.cs new file mode 100644 index 000000000..9530f24a1 --- /dev/null +++ b/codes/csharp/chapter_array_and_linkedlist/list.cs @@ -0,0 +1,76 @@ +using Newtonsoft.Json.Linq; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace hello_algo.chapter_array_and_linkedlist +{ + public class list + { + [Test] + public void Test() + { + + /* 初始化列表 */ + // 注意数组的元素类型是 int[] 的包装类 int[] + int[] numbers = new int[] { 1, 3, 2, 5, 4 }; + List list = numbers.ToList(); + Console.WriteLine("列表 list = " + string.Join(",",list)); + + /* 访问元素 */ + int num = list[1]; + Console.WriteLine("访问索引 1 处的元素,得到 num = " + num); + + /* 更新元素 */ + list[1]=0; + Console.WriteLine("将索引 1 处的元素更新为 0 ,得到 list = " + string.Join(",", list)); + + /* 清空列表 */ + list.Clear(); + Console.WriteLine("清空列表后 list = " + string.Join(",", list)); + + /* 尾部添加元素 */ + list.Add(1); + list.Add(3); + list.Add(2); + list.Add(5); + list.Add(4); + Console.WriteLine("添加元素后 list = " + string.Join(",", list)); + + /* 中间插入元素 */ + list.Insert(3, 6); + Console.WriteLine("在索引 3 处插入数字 6 ,得到 list = " + string.Join(",", list)); + + /* 删除元素 */ + list.Remove(3); + Console.WriteLine("删除索引 3 处的元素,得到 list = " + string.Join(",", list)); + + /* 通过索引遍历列表 */ + int count = 0; + for (int i = 0; i < list.Count(); i++) + { + count++; + } + + /* 直接遍历列表元素 */ + count = 0; + foreach (int n in list) + { + count++; + } + + /* 拼接两个列表 */ + List list1 = new() { 6, 8, 7, 10, 9 }; + list.AddRange(list1); + Console.WriteLine("将列表 list1 拼接到 list 之后,得到 list = " + string.Join(",", list)); + + /* 排序列表 */ + list.Sort(); // 排序后,列表元素从小到大排列 + Console.WriteLine("排序列表后 list = " + string.Join(",", list)); + } + } +} diff --git a/codes/csharp/chapter_array_and_linkedlist/my_list.cs b/codes/csharp/chapter_array_and_linkedlist/my_list.cs new file mode 100644 index 000000000..8ccf35954 --- /dev/null +++ b/codes/csharp/chapter_array_and_linkedlist/my_list.cs @@ -0,0 +1,164 @@ +using Newtonsoft.Json.Linq; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace hello_algo.chapter_array_and_linkedlist +{ + class MyList + { + private int[] nums; // 数组(存储列表元素) + private int capacity = 10; // 列表容量 + private int size = 0; // 列表长度(即当前元素数量) + private int extendRatio = 2; // 每次列表扩容的倍数 + + /* 构造函数 */ + public MyList() + { + nums = new int[capacity]; + } + + /* 获取列表长度(即当前元素数量)*/ + public int Size() + { + return size; + } + + /* 获取列表容量 */ + public int Capacity() + { + return capacity; + } + + /* 访问元素 */ + public int get(int index) + { + // 索引如果越界则抛出异常,下同 + if (index >= size) + throw new IndexOutOfRangeException("索引越界"); + return nums[index]; + } + + /* 更新元素 */ + public void set(int index, int num) + { + if (index >= size) + throw new IndexOutOfRangeException("索引越界"); + nums[index] = num; + } + + /* 尾部添加元素 */ + public void add(int num) + { + // 元素数量超出容量时,触发扩容机制 + if (size == Capacity()) + extendCapacity(); + nums[size] = num; + // 更新元素数量 + size++; + } + + /* 中间插入元素 */ + public void insert(int index, int num) + { + if (index >= size) + throw new IndexOutOfRangeException("索引越界"); + // 元素数量超出容量时,触发扩容机制 + if (size == Capacity()) + extendCapacity(); + // 将索引 index 以及之后的元素都向后移动一位 + for (int j = size - 1; j >= index; j--) + { + nums[j + 1] = nums[j]; + } + nums[index] = num; + // 更新元素数量 + size++; + } + + /* 删除元素 */ + public int remove(int index) + { + if (index >= size) + throw new IndexOutOfRangeException("索引越界"); + int num = nums[index]; + // 将索引 index 之后的元素都向前移动一位 + for (int j = index; j < size - 1; j++) + { + nums[j] = nums[j + 1]; + } + // 更新元素数量 + size--; + // 返回被删除元素 + return num; + } + + /* 列表扩容 */ + public void extendCapacity() + { + // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 + System.Array.Resize(ref nums, Capacity() * extendRatio); + // 更新列表容量 + capacity = nums.Length; + } + + /* 将列表转换为数组 */ + public int[] toArray() + { + int size = Size(); + // 仅转换有效长度范围内的列表元素 + int[] nums = new int[size]; + for (int i = 0; i < size; i++) + { + nums[i] = get(i); + } + return nums; + } + } + + public class my_list + { + [Test] + public void Test() + { + /* 初始化列表 */ + MyList list = new MyList(); + /* 尾部添加元素 */ + list.add(1); + list.add(3); + list.add(2); + list.add(5); + list.add(4); + Console.WriteLine("列表 list = " + string.Join(",", list.toArray()) + + " ,容量 = " + list.Capacity() + " ,长度 = " + list.Size()); + + /* 中间插入元素 */ + list.insert(3, 6); + Console.WriteLine("在索引 3 处插入数字 6 ,得到 list = " + string.Join(",", list.toArray())); + + /* 删除元素 */ + list.remove(3); + Console.WriteLine("删除索引 3 处的元素,得到 list = " + string.Join(",", list.toArray())); + + /* 访问元素 */ + int num = list.get(1); + Console.WriteLine("访问索引 1 处的元素,得到 num = " + num); + + /* 更新元素 */ + list.set(1, 0); + Console.WriteLine("将索引 1 处的元素更新为 0 ,得到 list = " + string.Join(",", list.toArray())); + + /* 测试扩容机制 */ + for (int i = 0; i < 10; i++) + { + // 在 i = 5 时,列表长度将超出列表容量,此时触发扩容机制 + list.add(i); + } + Console.WriteLine("扩容后的列表 list = " + string.Join(",", list.toArray()) + + " ,容量 = " + list.Capacity() + " ,长度 = " + list.Size()); + } + } +} diff --git a/codes/csharp/chapter_computational_complexity/leetcode_two_sum.cs b/codes/csharp/chapter_computational_complexity/leetcode_two_sum.cs new file mode 100644 index 000000000..d9498c329 --- /dev/null +++ b/codes/csharp/chapter_computational_complexity/leetcode_two_sum.cs @@ -0,0 +1,68 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace hello_algo.chapter_computational_complexity +{ + class SolutionBruteForce + { + public int[] twoSum(int[] nums, int target) + { + int size = nums.Length; + // 两层循环,时间复杂度 O(n^2) + for (int i = 0; i < size - 1; i++) + { + for (int j = i + 1; j < size; j++) + { + if (nums[i] + nums[j] == target) + return new int[] { i, j }; + } + } + return new int[0]; + } + } + + class SolutionHashMap + { + public int[] twoSum(int[] nums, int target) + { + int size = nums.Length; + // 辅助哈希表,空间复杂度 O(n) + Dictionary dic = new(); + // 单层循环,时间复杂度 O(n) + for (int i = 0; i < size; i++) + { + if (dic.ContainsKey(target - nums[i])) + { + return new int[] { dic[target - nums[i]], i }; + } + dic.Add(nums[i], i); + } + return new int[0]; + } + } + + public class leetcode_two_sum + { + [Test] + public void Test() + { + // ======= Test Case ======= + int[] nums = { 2, 7, 11, 15 }; + int target = 9; + + // ====== Driver Code ====== + // 方法一 + SolutionBruteForce slt1 = new SolutionBruteForce(); + int[] res = slt1.twoSum(nums, target); + Console.WriteLine("方法一 res = " + string.Join(",", res)); + // 方法二 + SolutionHashMap slt2 = new SolutionHashMap(); + res = slt2.twoSum(nums, target); + Console.WriteLine("方法二 res = " + string.Join(",", res)); + } + } +} diff --git a/codes/csharp/chapter_computational_complexity/space_complexity.cs b/codes/csharp/chapter_computational_complexity/space_complexity.cs new file mode 100644 index 000000000..97a16cdb8 --- /dev/null +++ b/codes/csharp/chapter_computational_complexity/space_complexity.cs @@ -0,0 +1,121 @@ +using hello_algo.include; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace hello_algo.chapter_computational_complexity +{ + public class space_complexity + { + /* 函数 */ + static int function() + { + // do something + return 0; + } + + /* 常数阶 */ + static void constant(int n) + { + // 常量、变量、对象占用 O(1) 空间 + int a = 0; + int b = 0; + int[] nums = new int[10000]; + ListNode node = new ListNode(0); + // 循环中的变量占用 O(1) 空间 + for (int i = 0; i < n; i++) + { + int c = 0; + } + // 循环中的函数占用 O(1) 空间 + for (int i = 0; i < n; i++) + { + function(); + } + } + + /* 线性阶 */ + static void linear(int n) + { + // 长度为 n 的数组占用 O(n) 空间 + int[] nums = new int[n]; + // 长度为 n 的列表占用 O(n) 空间 + List nodes = new(); + for (int i = 0; i < n; i++) + { + nodes.Add(new ListNode(i)); + } + // 长度为 n 的哈希表占用 O(n) 空间 + Dictionary map = new(); + for (int i = 0; i < n; i++) + { + map.Add(i, i.ToString()); + } + } + + /* 线性阶(递归实现) */ + static void linearRecur(int n) + { + Console.WriteLine("递归 n = " + n); + if (n == 1) return; + linearRecur(n - 1); + } + + /* 平方阶 */ + static void quadratic(int n) + { + // 矩阵占用 O(n^2) 空间 + int[,] numMatrix = new int[n, n]; + // 二维列表占用 O(n^2) 空间 + List> numList = new(); + for (int i = 0; i < n; i++) + { + List tmp = new(); + for (int j = 0; j < n; j++) + { + tmp.Add(0); + } + numList.Add(tmp); + } + } + + /* 平方阶(递归实现) */ + static int quadraticRecur(int n) + { + if (n <= 0) return 0; + int[] nums = new int[n]; + Console.WriteLine("递归 n = " + n + " 中的 nums 长度 = " + nums.Length); + return quadraticRecur(n - 1); + } + + /* 指数阶(建立满二叉树) */ + static TreeNode? buildTree(int n) + { + if (n == 0) return null; + TreeNode root = new TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; + } + + [Test] + public void Test() + { + int n = 5; + // 常数阶 + constant(n); + // 线性阶 + linear(n); + linearRecur(n); + // 平方阶 + quadratic(n); + quadraticRecur(n); + // 指数阶 + TreeNode? root = buildTree(n); + PrintUtil.printTree(root); + } + } +} diff --git a/codes/csharp/chapter_computational_complexity/time_complexity.cs b/codes/csharp/chapter_computational_complexity/time_complexity.cs new file mode 100644 index 000000000..71c1036e8 --- /dev/null +++ b/codes/csharp/chapter_computational_complexity/time_complexity.cs @@ -0,0 +1,226 @@ +using NUnit.Framework; + +namespace hello_algo.chapter_computational_complexity +{ + public class time_complexity + { + void algorithm(int n) + { + int a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + // +n(技巧 2) + for (int i = 0; i < 5 * n + 1; i++) + { + Console.WriteLine(0); + } + // +n*n(技巧 3) + for (int i = 0; i < 2 * n; i++) + { + for (int j = 0; j < n + 1; j++) + { + Console.WriteLine(0); + } + } + } + + // 算法 A 时间复杂度:常数阶 + void algorithm_A(int n) + { + Console.WriteLine(0); + } + // 算法 B 时间复杂度:线性阶 + void algorithm_B(int n) + { + for (int i = 0; i < n; i++) + { + Console.WriteLine(0); + } + } + // 算法 C 时间复杂度:常数阶 + void algorithm_C(int n) + { + for (int i = 0; i < 1000000; i++) + { + Console.WriteLine(0); + } + } + + /* 常数阶 */ + static int constant(int n) + { + int count = 0; + int size = 100000; + for (int i = 0; i < size; i++) + count++; + return count; + } + + /* 线性阶 */ + static int linear(int n) + { + int count = 0; + for (int i = 0; i < n; i++) + count++; + return count; + } + + /* 线性阶(遍历数组) */ + static int arrayTraversal(int[] nums) + { + int count = 0; + // 循环次数与数组长度成正比 + foreach (int num in nums) + { + count++; + } + return count; + } + + /* 平方阶 */ + static int quadratic(int n) + { + int count = 0; + // 循环次数与数组长度成平方关系 + for (int i = 0; i < n; i++) + { + for (int j = 0; j < n; j++) + { + count++; + } + } + return count; + } + + /* 平方阶(冒泡排序) */ + static int bubbleSort(int[] nums) + { + int count = 0; // 计数器 + // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + for (int i = nums.Length - 1; i > 0; i--) + { + // 内循环:冒泡操作 + for (int j = 0; j < i; j++) + { + if (nums[j] > nums[j + 1]) + { + // 交换 nums[j] 与 nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 元素交换包含 3 个单元操作 + } + } + } + return count; + } + + /* 指数阶(循环实现) */ + static int exponential(int n) + { + int count = 0, bas = 1; + // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) + for (int i = 0; i < n; i++) + { + for (int j = 0; j < bas; j++) + { + count++; + } + bas *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; + } + + /* 指数阶(递归实现) */ + static int expRecur(int n) + { + if (n == 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; + } + + /* 对数阶(循环实现) */ + static int logarithmic(float n) + { + int count = 0; + while (n > 1) + { + n = n / 2; + count++; + } + return count; + } + + /* 对数阶(递归实现) */ + static int logRecur(float n) + { + if (n <= 1) return 0; + return logRecur(n / 2) + 1; + } + + /* 线性对数阶 */ + static int linearLogRecur(float n) + { + if (n <= 1) return 1; + int count = linearLogRecur(n / 2) + + linearLogRecur(n / 2); + for (int i = 0; i < n; i++) + { + count++; + } + return count; + } + + /* 阶乘阶(递归实现) */ + static int factorialRecur(int n) + { + if (n == 0) return 1; + int count = 0; + // 从 1 个分裂出 n 个 + for (int i = 0; i < n; i++) + { + count += factorialRecur(n - 1); + } + return count; + } + + [Test] + public void Test() + { + // 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 + int n = 8; + Console.WriteLine("输入数据大小 n = " + n); + + int count = constant(n); + Console.WriteLine("常数阶的计算操作数量 = " + count); + + count = linear(n); + Console.WriteLine("线性阶的计算操作数量 = " + count); + count = arrayTraversal(new int[n]); + Console.WriteLine("线性阶(遍历数组)的计算操作数量 = " + count); + + count = quadratic(n); + Console.WriteLine("平方阶的计算操作数量 = " + count); + int[] nums = new int[n]; + for (int i = 0; i < n; i++) + nums[i] = n - i; // [n,n-1,...,2,1] + count = bubbleSort(nums); + Console.WriteLine("平方阶(冒泡排序)的计算操作数量 = " + count); + + count = exponential(n); + Console.WriteLine("指数阶(循环实现)的计算操作数量 = " + count); + count = expRecur(n); + Console.WriteLine("指数阶(递归实现)的计算操作数量 = " + count); + + count = logarithmic((float)n); + Console.WriteLine("对数阶(循环实现)的计算操作数量 = " + count); + count = logRecur((float)n); + Console.WriteLine("对数阶(递归实现)的计算操作数量 = " + count); + + count = linearLogRecur((float)n); + Console.WriteLine("线性对数阶(递归实现)的计算操作数量 = " + count); + + count = factorialRecur(n); + Console.WriteLine("阶乘阶(递归实现)的计算操作数量 = " + count); + } + } +} diff --git a/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs b/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs new file mode 100644 index 000000000..ebbcbeb00 --- /dev/null +++ b/codes/csharp/chapter_computational_complexity/worst_best_time_complexity.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace hello_algo.chapter_computational_complexity +{ + public class worst_best_time_complexity + { + /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ + static int[] randomNumbers(int n) + { + int[] nums = new int[n]; + // 生成数组 nums = { 1, 2, 3, ..., n } + for (int i = 0; i < n; i++) + { + nums[i] = i + 1; + } + + // 随机打乱数组元素 + for (int i = 0; i < nums.Length; i++) + { + var index = new Random().Next(i, nums.Length); + var tmp = nums[i]; + var ran = nums[index]; + nums[i] = ran; + nums[index] = tmp; + } + return nums; + } + + /* 查找数组 nums 中数字 1 所在索引 */ + static int findOne(int[] nums) + { + for (int i = 0; i < nums.Length; i++) + { + if (nums[i] == 1) + return i; + } + return -1; + } + + + /* Driver Code */ + public static void main(String[] args) + { + for (int i = 0; i < 10; i++) + { + int n = 100; + int[] nums = randomNumbers(n); + int index = findOne(nums); + Console.WriteLine("\n数组 [ 1, 2, ..., n ] 被打乱后 = " + string.Join(",", nums)); + Console.WriteLine("数字 1 的索引为 " + index); + } + } + } +} diff --git a/codes/csharp/chapter_hashing/array_hash_map.cs b/codes/csharp/chapter_hashing/array_hash_map.cs new file mode 100644 index 000000000..cc564e26b --- /dev/null +++ b/codes/csharp/chapter_hashing/array_hash_map.cs @@ -0,0 +1,161 @@ + +using hello_algo.include; +using NUnit.Framework; +using System.Collections.Immutable; + +namespace hello_algo.chapter_hashing +{ + + /* 键值对 int->String */ + class Entry + { + public int key; + public String val; + public Entry(int key, String val) + { + this.key = key; + this.val = val; + } + } + + /* 基于数组简易实现的哈希表 */ + class ArrayHashMap + { + private List bucket; + public ArrayHashMap() + { + // 初始化一个长度为 100 的桶(数组) + bucket = new (); + for (int i = 0; i < 100; i++) + { + bucket.Add(null); + } + } + + /* 哈希函数 */ + private int hashFunc(int key) + { + int index = key % 100; + return index; + } + + /* 查询操作 */ + public String? get(int key) + { + int index = hashFunc(key); + Entry? pair = bucket[index]; + if (pair == null) return null; + return pair.val; + } + + /* 添加操作 */ + public void put(int key, String val) + { + Entry pair = new Entry(key, val); + int index = hashFunc(key); + bucket[index]=pair; + } + + /* 删除操作 */ + public void remove(int key) + { + int index = hashFunc(key); + // 置为 null ,代表删除 + bucket[index]=null; + } + + /* 获取所有键值对 */ + public List entrySet() + { + List entrySet = new (); + foreach (Entry? pair in bucket) + { + if (pair != null) + entrySet.Add(pair); + } + return entrySet; + } + + /* 获取所有键 */ + public List keySet() + { + List keySet = new (); + foreach (Entry? pair in bucket) + { + if (pair != null) + keySet.Add(pair.key); + } + return keySet; + } + + /* 获取所有值 */ + public List valueSet() + { + List valueSet = new (); + foreach (Entry? pair in bucket) + { + if (pair != null) + valueSet.Add(pair.val); + } + return valueSet; + } + + /* 打印哈希表 */ + public void print() + { + foreach (Entry kv in entrySet()) + { + Console.WriteLine(kv.key + " -> " + kv.val); + } + } + } + + + public class array_hash_map + { + [Test] + public void Test() + { + /* 初始化哈希表 */ + ArrayHashMap map = new ArrayHashMap(); + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.put(12836, "小哈"); + map.put(15937, "小啰"); + map.put(16750, "小算"); + map.put(13276, "小法"); + map.put(10583, "小鸭"); + Console.WriteLine("\n添加完成后,哈希表为\nKey -> Value"); + map.print(); + + /* 查询操作 */ + // 向哈希表输入键 key ,得到值 value + String? name = map.get(15937); + Console.WriteLine("\n输入学号 15937 ,查询到姓名 " + name); + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.remove(10583); + Console.WriteLine("\n删除 10583 后,哈希表为\nKey -> Value"); + map.print(); + + /* 遍历哈希表 */ + Console.WriteLine("\n遍历键值对 Key->Value"); + foreach (Entry kv in map.entrySet()) + { + Console.WriteLine(kv.key + " -> " + kv.val); + } + Console.WriteLine("\n单独遍历键 Key"); + foreach (int key in map.keySet()) + { + Console.WriteLine(key); + } + Console.WriteLine("\n单独遍历值 Value"); + foreach (String val in map.valueSet()) + { + Console.WriteLine(val); + } + } + } +} \ No newline at end of file diff --git a/codes/csharp/chapter_hashing/hash_map.cs b/codes/csharp/chapter_hashing/hash_map.cs new file mode 100644 index 000000000..ff86c88a5 --- /dev/null +++ b/codes/csharp/chapter_hashing/hash_map.cs @@ -0,0 +1,52 @@ + +using hello_algo.include; +using NUnit.Framework; +using System.Collections.Immutable; + +namespace hello_algo.chapter_hashing +{ + + public class hash_map { + [Test] + public void Test() + { + /* 初始化哈希表 */ + Dictionary map = new (); + + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.Add(12836, "小哈"); + map.Add(15937, "小啰"); + map.Add(16750, "小算"); + map.Add(13276, "小法"); + map.Add(10583, "小鸭"); + Console.WriteLine("\n添加完成后,哈希表为\nKey -> Value"); + PrintUtil.printHashMap(map); + + /* 查询操作 */ + // 向哈希表输入键 key ,得到值 value + String name = map[15937]; + Console.WriteLine("\n输入学号 15937 ,查询到姓名 " + name); + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.Remove(10583); + Console.WriteLine("\n删除 10583 后,哈希表为\nKey -> Value"); + PrintUtil.printHashMap(map); + + /* 遍历哈希表 */ + Console.WriteLine("\n遍历键值对 Key->Value"); + foreach (var kv in map) { + Console.WriteLine(kv.Key + " -> " + kv.Value); + } + Console.WriteLine("\n单独遍历键 Key"); + foreach (int key in map.Keys) { + Console.WriteLine(key); + } + Console.WriteLine("\n单独遍历值 Value"); + foreach (String val in map.Values) { + Console.WriteLine(val); + } + } + } +} diff --git a/codes/csharp/chapter_searching/binary_search.cs b/codes/csharp/chapter_searching/binary_search.cs new file mode 100644 index 000000000..bfe688c4d --- /dev/null +++ b/codes/csharp/chapter_searching/binary_search.cs @@ -0,0 +1,67 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace hello_algo.chapter_searching +{ + public class binary_search + { + /* 二分查找(双闭区间) */ + static int binarySearch(int[] nums, int target) + { + // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 + int i = 0, j = nums.Length - 1; + // 循环,当搜索区间为空时跳出(当 i > j 时为空) + while (i <= j) + { + int m = (i + j) / 2; // 计算中点索引 m + if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j] 中 + i = m + 1; + else if (nums[m] > target) // 此情况说明 target 在区间 [i, m-1] 中 + j = m - 1; + else // 找到目标元素,返回其索引 + return m; + } + // 未找到目标元素,返回 -1 + return -1; + } + + /* 二分查找(左闭右开) */ + static int binarySearch1(int[] nums, int target) + { + // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 + int i = 0, j = nums.Length; + // 循环,当搜索区间为空时跳出(当 i = j 时为空) + while (i < j) + { + int m = (i + j) / 2; // 计算中点索引 m + if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j) 中 + i = m + 1; + else if (nums[m] > target) // 此情况说明 target 在区间 [i, m) 中 + j = m; + else // 找到目标元素,返回其索引 + return m; + } + // 未找到目标元素,返回 -1 + return -1; + } + + [Test] + public void Test() + { + int target = 6; + int[] nums = { 1, 3, 6, 8, 12, 15, 23, 67, 70, 92 }; + + /* 二分查找(双闭区间) */ + int index = binarySearch(nums, target); + Console.WriteLine("目标元素 6 的索引 = " + index); + + /* 二分查找(左闭右开) */ + index = binarySearch1(nums, target); + Console.WriteLine("目标元素 6 的索引 = " + index); + } + } +} diff --git a/codes/csharp/chapter_searching/hashing_search.cs b/codes/csharp/chapter_searching/hashing_search.cs new file mode 100644 index 000000000..6cc5f3481 --- /dev/null +++ b/codes/csharp/chapter_searching/hashing_search.cs @@ -0,0 +1,60 @@ +using hello_algo.include; +using NUnit.Framework; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace hello_algo.chapter_searching +{ + public class hashing_search + { + /* 哈希查找(数组) */ + static int hashingSearch(Dictionary map, int target) + { + // 哈希表的 key: 目标元素,value: 索引 + // 若哈希表中无此 key ,返回 -1 + return map.GetValueOrDefault(target, -1); + } + + /* 哈希查找(链表) */ + static ListNode? hashingSearch1(Dictionary map, int target) + { + + // 哈希表的 key: 目标结点值,value: 结点对象 + // 若哈希表中无此 key ,返回 null + return map.GetValueOrDefault(target); + } + + [Test] + public void Test() + { + int target = 3; + + /* 哈希查找(数组) */ + int[] nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; + // 初始化哈希表 + Dictionary map = new(); + for (int i = 0; i < nums.Length; i++) + { + map[nums[i]] = i; // key: 元素,value: 索引 + } + int index = hashingSearch(map, target); + Console.WriteLine("目标元素 3 的索引 = " + index); + + /* 哈希查找(链表) */ + ListNode head = ListNode.ArrToLinkedList(nums); + // 初始化哈希表 + Dictionary map1 = new(); + while (head != null) + { + map1[head.val] = head; // key: 结点值,value: 结点 + head = head.next; + } + ListNode? node = hashingSearch1(map1, target); + Console.WriteLine("目标结点值 3 的对应结点对象为 " + node); + } + } +} diff --git a/codes/csharp/chapter_searching/linear_search.cs b/codes/csharp/chapter_searching/linear_search.cs new file mode 100644 index 000000000..ab22d8d2b --- /dev/null +++ b/codes/csharp/chapter_searching/linear_search.cs @@ -0,0 +1,58 @@ +using hello_algo.include; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace hello_algo.chapter_searching +{ + public class linear_search + { + /* 线性查找(数组) */ + static int linearSearch(int[] nums, int target) + { + // 遍历数组 + for (int i = 0; i < nums.Length; i++) + { + // 找到目标元素,返回其索引 + if (nums[i] == target) + return i; + } + // 未找到目标元素,返回 -1 + return -1; + } + + /* 线性查找(链表) */ + static ListNode? linearSearch(ListNode head, int target) + { + // 遍历链表 + while (head != null) + { + // 找到目标结点,返回之 + if (head.val == target) + return head; + head = head.next; + } + // 未找到目标结点,返回 null + return null; + } + + [Test] + public void Test() + { + int target = 3; + + /* 在数组中执行线性查找 */ + int[] nums = { 1, 5, 3, 2, 4, 7, 5, 9, 10, 8 }; + int index = linearSearch(nums, target); + Console.WriteLine("目标元素 3 的索引 = " + index); + + /* 在链表中执行线性查找 */ + ListNode head = ListNode.ArrToLinkedList(nums); + ListNode? node = linearSearch(head, target); + Console.WriteLine("目标结点值 3 的对应结点对象为 " + node); + } + } +} diff --git a/codes/csharp/chapter_sorting/bubble_sort.cs b/codes/csharp/chapter_sorting/bubble_sort.cs new file mode 100644 index 000000000..3991f3aa4 --- /dev/null +++ b/codes/csharp/chapter_sorting/bubble_sort.cs @@ -0,0 +1,67 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace hello_algo.chapter_sorting +{ + public class bubble_sort + { + /* 冒泡排序 */ + static void bubbleSort(int[] nums) + { + // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + for (int i = nums.Length - 1; i > 0; i--) + { + // 内循环:冒泡操作 + for (int j = 0; j < i; j++) + { + if (nums[j] > nums[j + 1]) + { + // 交换 nums[j] 与 nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } + } + + /* 冒泡排序(标志优化)*/ + static void bubbleSortWithFlag(int[] nums) + { + // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + for (int i = nums.Length - 1; i > 0; i--) + { + bool flag = false; // 初始化标志位 + // 内循环:冒泡操作 + for (int j = 0; j < i; j++) + { + if (nums[j] > nums[j + 1]) + { + // 交换 nums[j] 与 nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; // 记录交换元素 + } + } + if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 + } + } + + [Test] + public void Test() + { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + bubbleSort(nums); + Console.WriteLine("冒泡排序完成后 nums = " + string.Join(",",nums)); + + int[] nums1 = { 4, 1, 3, 1, 5, 2 }; + bubbleSortWithFlag(nums1); + Console.WriteLine("冒泡排序完成后 nums1 = " + string.Join(",", nums)); + } + } +} diff --git a/codes/csharp/chapter_sorting/insertion_sort.cs b/codes/csharp/chapter_sorting/insertion_sort.cs new file mode 100644 index 000000000..2e03b4fb9 --- /dev/null +++ b/codes/csharp/chapter_sorting/insertion_sort.cs @@ -0,0 +1,32 @@ +using NUnit.Framework; + +namespace hello_algo.chapter_sorting +{ + public class insertion_sort + { + /* 插入排序 */ + static void insertionSort(int[] nums) + { + // 外循环:base = nums[1], nums[2], ..., nums[n-1] + for (int i = 1; i < nums.Length; i++) + { + int bas = nums[i], j = i - 1; + // 内循环:将 base 插入到左边的正确位置 + while (j >= 0 && nums[j] > bas) + { + nums[j + 1] = nums[j]; // 1. 将 nums[j] 向右移动一位 + j--; + } + nums[j + 1] = bas; // 2. 将 base 赋值到正确位置 + } + } + + [Test] + public void Test() + { + int[] nums = { 4, 1, 3, 1, 5, 2 }; + insertionSort(nums); + Console.WriteLine("插入排序完成后 nums = " + string.Join(",", nums)); + } + } +} diff --git a/codes/csharp/chapter_sorting/merge_sort.cs b/codes/csharp/chapter_sorting/merge_sort.cs new file mode 100644 index 000000000..11b45829e --- /dev/null +++ b/codes/csharp/chapter_sorting/merge_sort.cs @@ -0,0 +1,64 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace hello_algo.chapter_sorting +{ + public class merge_sort + { + /** + * 合并左子数组和右子数组 + * 左子数组区间 [left, mid] + * 右子数组区间 [mid + 1, right] + */ + static void merge(int[] nums, int left, int mid, int right) + { + // 初始化辅助数组 + int[] tmp = nums[left..(right + 1)];//Array.CopyOfRange(nums, left, right + 1); + // 左子数组的起始索引和结束索引 + int leftStart = left - left, leftEnd = mid - left; + // 右子数组的起始索引和结束索引 + int rightStart = mid + 1 - left, rightEnd = right - left; + // i, j 分别指向左子数组、右子数组的首元素 + int i = leftStart, j = rightStart; + // 通过覆盖原数组 nums 来合并左子数组和右子数组 + for (int k = left; k <= right; k++) + { + // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ + if (i > leftEnd) + nums[k] = tmp[j++]; + // 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++ + else if (j > rightEnd || tmp[i] <= tmp[j]) + nums[k] = tmp[i++]; + // 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + else + nums[k] = tmp[j++]; + } + } + + /* 归并排序 */ + static void mergeSort(int[] nums, int left, int right) + { + // 终止条件 + if (left >= right) return; // 当子数组长度为 1 时终止递归 + // 划分阶段 + int mid = (left + right) / 2; // 计算中点 + mergeSort(nums, left, mid); // 递归左子数组 + mergeSort(nums, mid + 1, right); // 递归右子数组 + // 合并阶段 + merge(nums, left, mid, right); + } + + [Test] + public void Test() + { + /* 归并排序 */ + int[] nums = { 7, 3, 2, 6, 0, 1, 5, 4 }; + mergeSort(nums, 0, nums.Length - 1); + Console.WriteLine("归并排序完成后 nums = " + string.Join(",", nums)); + } + } +} diff --git a/codes/csharp/chapter_sorting/quick_sort.cs b/codes/csharp/chapter_sorting/quick_sort.cs new file mode 100644 index 000000000..ed9c37e0c --- /dev/null +++ b/codes/csharp/chapter_sorting/quick_sort.cs @@ -0,0 +1,182 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace hello_algo.chapter_sorting +{ + class QuickSort + { + /* 元素交换 */ + static void swap(int[] nums, int i, int j) + { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵划分 */ + static int partition(int[] nums, int left, int right) + { + // 以 nums[left] 作为基准数 + int i = left, j = right; + while (i < j) + { + while (i < j && nums[j] >= nums[left]) + j--; // 从右向左找首个小于基准数的元素 + while (i < j && nums[i] <= nums[left]) + i++; // 从左向右找首个大于基准数的元素 + swap(nums, i, j); // 交换这两个元素 + } + swap(nums, i, left); // 将基准数交换至两子数组的分界线 + return i; // 返回基准数的索引 + } + + /* 快速排序 */ + public static void quickSort(int[] nums, int left, int right) + { + // 子数组长度为 1 时终止递归 + if (left >= right) + return; + // 哨兵划分 + int pivot = partition(nums, left, right); + // 递归左子数组、右子数组 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } + } + + /* 快速排序类(中位基准数优化) */ + class QuickSortMedian + { + /* 元素交换 */ + static void swap(int[] nums, int i, int j) + { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 选取三个元素的中位数 */ + static int medianThree(int[] nums, int left, int mid, int right) + { + // 使用了异或操作来简化代码 + // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 + if ((nums[left] > nums[mid]) ^ (nums[left] > nums[right])) + return left; + else if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right])) + return mid; + else + return right; + } + + /* 哨兵划分(三数取中值) */ + static int partition(int[] nums, int left, int right) + { + // 选取三个候选元素的中位数 + int med = medianThree(nums, left, (left + right) / 2, right); + // 将中位数交换至数组最左端 + swap(nums, left, med); + // 以 nums[left] 作为基准数 + int i = left, j = right; + while (i < j) + { + while (i < j && nums[j] >= nums[left]) + j--; // 从右向左找首个小于基准数的元素 + while (i < j && nums[i] <= nums[left]) + i++; // 从左向右找首个大于基准数的元素 + swap(nums, i, j); // 交换这两个元素 + } + swap(nums, i, left); // 将基准数交换至两子数组的分界线 + return i; // 返回基准数的索引 + } + + /* 快速排序 */ + public static void quickSort(int[] nums, int left, int right) + { + // 子数组长度为 1 时终止递归 + if (left >= right) + return; + // 哨兵划分 + int pivot = partition(nums, left, right); + // 递归左子数组、右子数组 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } + } + + /* 快速排序类(尾递归优化) */ + class QuickSortTailCall + { + /* 元素交换 */ + static void swap(int[] nums, int i, int j) + { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵划分 */ + static int partition(int[] nums, int left, int right) + { + // 以 nums[left] 作为基准数 + int i = left, j = right; + while (i < j) + { + while (i < j && nums[j] >= nums[left]) + j--; // 从右向左找首个小于基准数的元素 + while (i < j && nums[i] <= nums[left]) + i++; // 从左向右找首个大于基准数的元素 + swap(nums, i, j); // 交换这两个元素 + } + swap(nums, i, left); // 将基准数交换至两子数组的分界线 + return i; // 返回基准数的索引 + } + + /* 快速排序(尾递归优化) */ + public static void quickSort(int[] nums, int left, int right) + { + // 子数组长度为 1 时终止 + while (left < right) + { + // 哨兵划分操作 + int pivot = partition(nums, left, right); + // 对两个子数组中较短的那个执行快排 + if (pivot - left < right - pivot) + { + quickSort(nums, left, pivot - 1); // 递归排序左子数组 + left = pivot + 1; // 剩余待排序区间为 [pivot + 1, right] + } + else + { + quickSort(nums, pivot + 1, right); // 递归排序右子数组 + right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1] + } + } + } + } + + public class quick_sort + { + [Test] + public void Test() + { + /* 快速排序 */ + int[] nums = { 2, 4, 1, 0, 3, 5 }; + QuickSort.quickSort(nums, 0, nums.Length - 1); + Console.WriteLine("快速排序完成后 nums = " + string.Join(",", nums)); + + /* 快速排序(中位基准数优化) */ + int[] nums1 = { 2, 4, 1, 0, 3, 5 }; + QuickSortMedian.quickSort(nums1, 0, nums1.Length - 1); + Console.WriteLine("快速排序(中位基准数优化)完成后 nums1 = " + string.Join(",", nums1)); + + /* 快速排序(尾递归优化) */ + int[] nums2 = { 2, 4, 1, 0, 3, 5 }; + QuickSortTailCall.quickSort(nums2, 0, nums2.Length - 1); + Console.WriteLine("快速排序(尾递归优化)完成后 nums2 = " + string.Join(",", nums2)); + } + } +} diff --git a/codes/csharp/chapter_stack_and_queue/array_queue.cs b/codes/csharp/chapter_stack_and_queue/array_queue.cs new file mode 100644 index 000000000..dc6b1403e --- /dev/null +++ b/codes/csharp/chapter_stack_and_queue/array_queue.cs @@ -0,0 +1,116 @@ +using System; +using hello_algo.include; +using NUnit.Framework; + +namespace hello_algo.chapter_stack_and_queue +{ + + /* 基于环形数组实现的队列 */ + class ArrayQueue { + private int[] nums; // 用于存储队列元素的数组 + private int front = 0; // 头指针,指向队首 + private int rear = 0; // 尾指针,指向队尾 + 1 + + public ArrayQueue(int capacity) { + // 初始化数组 + nums = new int[capacity]; + } + + /* 获取队列的容量 */ + public int capacity() { + return nums.Length; + } + + /* 获取队列的长度 */ + public int size() { + int capacity = this.capacity(); + // 由于将数组看作为环形,可能 rear < front ,因此需要取余数 + return (capacity + rear - front) % capacity; + } + + /* 判断队列是否为空 */ + public bool isEmpty() { + return rear - front == 0; + } + + /* 入队 */ + public void offer(int num) { + if (size() == capacity()) { + Console.WriteLine("队列已满"); + return; + } + // 尾结点后添加 num + nums[rear] = num; + // 尾指针向后移动一位,越过尾部后返回到数组头部 + rear = (rear + 1) % capacity(); + } + + /* 出队 */ + public int poll() { + int num = peek(); + // 队头指针向后移动一位,若越过尾部则返回到数组头部 + front = (front + 1) % capacity(); + return num; + } + + /* 访问队首元素 */ + public int peek() { + if (isEmpty()) + throw new Exception(); + return nums[front]; + } + + /* 返回数组 */ + public int[] toArray() { + int size = this.size(); + int capacity = this.capacity(); + // 仅转换有效长度范围内的列表元素 + int[] res = new int[size]; + for (int i = 0, j = front; i < size; i++, j++) { + res[i] = nums[j % capacity]; + } + return res; + } + } + + public class array_queue { + [Test] + public void Test() + { + /* 初始化队列 */ + int capacity = 10; + ArrayQueue queue = new ArrayQueue(capacity); + + /* 元素入队 */ + queue.offer(1); + queue.offer(3); + queue.offer(2); + queue.offer(5); + queue.offer(4); + Console.WriteLine("队列 queue = " + string.Join(",",queue.toArray())); + + /* 访问队首元素 */ + int peek = queue.peek(); + Console.WriteLine("队首元素 peek = " + peek); + + /* 元素出队 */ + int poll = queue.poll(); + Console.WriteLine("出队元素 poll = " + poll + ",出队后 queue = " + string.Join(",", queue.toArray())); + + /* 获取队列的长度 */ + int size = queue.size(); + Console.WriteLine("队列长度 size = " + size); + + /* 判断队列是否为空 */ + bool isEmpty = queue.isEmpty(); + Console.WriteLine("队列是否为空 = " + isEmpty); + + /* 测试环形数组 */ + for (int i = 0; i < 10; i++) { + queue.offer(i); + queue.poll(); + Console.WriteLine("第 " + i + " 轮入队 + 出队后 queue = " + string.Join(",", queue.toArray())); + } + } + } +} \ No newline at end of file diff --git a/codes/csharp/chapter_stack_and_queue/array_stack.cs b/codes/csharp/chapter_stack_and_queue/array_stack.cs new file mode 100644 index 000000000..5ce2dae6d --- /dev/null +++ b/codes/csharp/chapter_stack_and_queue/array_stack.cs @@ -0,0 +1,94 @@ +using System; +using hello_algo.include; +using NUnit.Framework; + +namespace hello_algo.chapter_stack_and_queue +{ + + /* 基于数组实现的栈 */ + class ArrayStack + { + private List stack; + public ArrayStack() + { + // 初始化列表(动态数组) + stack = new(); + } + + /* 获取栈的长度 */ + public int size() + { + return stack.Count(); + } + + /* 判断栈是否为空 */ + public bool isEmpty() + { + return size() == 0; + } + + /* 入栈 */ + public void push(int num) + { + stack.Add(num); + } + + /* 出栈 */ + public int pop() + { + if (isEmpty()) + throw new Exception(); + var val = peek(); + stack.RemoveAt(size() - 1); + return val; + } + + /* 访问栈顶元素 */ + public int peek() + { + if (isEmpty()) + throw new Exception(); + return stack[size() - 1]; + } + + /* 将 List 转化为 Array 并返回 */ + public int[] toArray() + { + return stack.ToArray(); + } + } + + public class array_stack + { + [Test] + public void Test() + { + /* 初始化栈 */ + ArrayStack stack = new ArrayStack(); + + /* 元素入栈 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + Console.WriteLine("栈 stack = " + String.Join(",", stack.toArray())); + + /* 访问栈顶元素 */ + int peek = stack.peek(); + Console.WriteLine("栈顶元素 peek = " + peek); + + /* 元素出栈 */ + int pop = stack.pop(); + Console.WriteLine("出栈元素 pop = " + pop + ",出栈后 stack = " + String.Join(",", stack.toArray())); + + /* 获取栈的长度 */ + int size = stack.size(); + Console.WriteLine("栈的长度 size = " + size); + + /* 判断是否为空 */ + bool isEmpty = stack.isEmpty(); + Console.WriteLine("栈是否为空 = " + isEmpty); + } + } +} diff --git a/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs b/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs new file mode 100644 index 000000000..8d7c2796f --- /dev/null +++ b/codes/csharp/chapter_stack_and_queue/linkedlist_queue.cs @@ -0,0 +1,119 @@ +using System; +using hello_algo.include; +using NUnit.Framework; + +namespace hello_algo.chapter_stack_and_queue +{ + /* 基于链表实现的队列 */ + class LinkedListQueue + { + private ListNode? front, rear; // 头结点 front ,尾结点 rear + private int queSize = 0; + + public LinkedListQueue() + { + front = null; + rear = null; + } + + /* 获取队列的长度 */ + public int size() + { + return queSize; + } + + /* 判断队列是否为空 */ + public bool isEmpty() + { + return size() == 0; + } + + /* 入队 */ + public void offer(int num) + { + // 尾结点后添加 num + ListNode node = new ListNode(num); + // 如果队列为空,则令头、尾结点都指向该结点 + if (front == null) + { + front = node; + rear = node; + // 如果队列不为空,则将该结点添加到尾结点后 + } + else if (rear != null) + { + rear.next = node; + rear = node; + } + queSize++; + } + + /* 出队 */ + public int poll() + { + int num = peek(); + // 删除头结点 + front = front?.next; + queSize--; + return num; + } + + /* 访问队首元素 */ + public int peek() + { + if (size() == 0 || front == null) + throw new Exception(); + return front.val; + } + + /* 将链表转化为 Array 并返回 */ + public int[] toArray() + { + if (front == null) + return Array.Empty(); + + ListNode node = front; + int[] res = new int[size()]; + for (int i = 0; i < res.Length; i++) + { + res[i] = node.val; + node = node.next; + } + return res; + } + } + + public class linkedlist_queue + { + [Test] + public void Test() + { + /* 初始化队列 */ + LinkedListQueue queue = new LinkedListQueue(); + + /* 元素入队 */ + queue.offer(1); + queue.offer(3); + queue.offer(2); + queue.offer(5); + queue.offer(4); + Console.WriteLine("队列 queue = " + String.Join(",", queue.toArray())); + + /* 访问队首元素 */ + int peek = queue.peek(); + Console.WriteLine("队首元素 peek = " + peek); + + /* 元素出队 */ + int poll = queue.poll(); + Console.WriteLine("出队元素 poll = " + poll + ",出队后 queue = " + String.Join(",", queue.toArray())); + + /* 获取队列的长度 */ + int size = queue.size(); + Console.WriteLine("队列长度 size = " + size); + + /* 判断队列是否为空 */ + bool isEmpty = queue.isEmpty(); + Console.WriteLine("队列是否为空 = " + isEmpty); + } + } +} \ No newline at end of file diff --git a/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs b/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs new file mode 100644 index 000000000..5ed82f2e0 --- /dev/null +++ b/codes/csharp/chapter_stack_and_queue/linkedlist_stack.cs @@ -0,0 +1,108 @@ +using System; +using hello_algo.include; +using NUnit.Framework; + +namespace hello_algo.chapter_stack_and_queue +{ + class LinkedListStack + { + private ListNode? stackPeek; // 将头结点作为栈顶 + private int stkSize = 0; // 栈的长度 + + public LinkedListStack() + { + stackPeek = null; + } + + /* 获取栈的长度 */ + public int size() + { + return stkSize; + } + + /* 判断栈是否为空 */ + public bool isEmpty() + { + return size() == 0; + } + + /* 入栈 */ + public void push(int num) + { + ListNode node = new ListNode(num); + node.next = stackPeek; + stackPeek = node; + stkSize++; + } + + /* 出栈 */ + public int pop() + { + if (stackPeek == null) + throw new Exception(); + + int num = peek(); + stackPeek = stackPeek.next; + stkSize--; + return num; + } + + /* 访问栈顶元素 */ + public int peek() + { + if (size() == 0 || stackPeek==null) + throw new Exception(); + return stackPeek.val; + } + + /* 将 List 转化为 Array 并返回 */ + public int[] toArray() + { + if (stackPeek == null) + return Array.Empty(); + + ListNode node = stackPeek; + int[] res = new int[size()]; + for (int i = res.Length - 1; i >= 0; i--) + { + res[i] = node.val; + node = node.next; + } + return res; + } + } + + public class linkedlist_stack + { + [Test] + public void Test() + { + /* 初始化栈 */ + LinkedListStack stack = new LinkedListStack(); + + /* 元素入栈 */ + stack.push(1); + stack.push(3); + stack.push(2); + stack.push(5); + stack.push(4); + Console.WriteLine("栈 stack = " + String.Join(",",stack.toArray())); + + /* 访问栈顶元素 */ + int peek = stack.peek(); + Console.WriteLine("栈顶元素 peek = " + peek); + + /* 元素出栈 */ + int pop = stack.pop(); + Console.WriteLine("出栈元素 pop = " + pop + ",出栈后 stack = " + String.Join(",",stack.toArray())); + + /* 获取栈的长度 */ + int size = stack.size(); + Console.WriteLine("栈的长度 size = " + size); + + /* 判断是否为空 */ + bool isEmpty = stack.isEmpty(); + Console.WriteLine("栈是否为空 = " + isEmpty); + } + } +} \ No newline at end of file diff --git a/codes/csharp/chapter_stack_and_queue/queue.cs b/codes/csharp/chapter_stack_and_queue/queue.cs new file mode 100644 index 000000000..d1ddbce99 --- /dev/null +++ b/codes/csharp/chapter_stack_and_queue/queue.cs @@ -0,0 +1,41 @@ +using System; +using hello_algo.include; +using NUnit.Framework; + +namespace hello_algo.chapter_stack_and_queue +{ + public class queue + { + [Test] + public void Test() + { + /* 初始化队列 */ + Queue queue = new(); + + /* 元素入队 */ + queue.Enqueue(1); + queue.Enqueue(3); + queue.Enqueue(2); + queue.Enqueue(5); + queue.Enqueue(4); + Console.WriteLine("队列 queue = " + String.Join(",", queue.ToArray())); + + /* 访问队首元素 */ + int peek = queue.Peek(); + Console.WriteLine("队首元素 peek = " + peek); + + /* 元素出队 */ + int poll = queue.Dequeue(); + Console.WriteLine("出队元素 poll = " + poll + ",出队后 queue = " + String.Join(",", queue.ToArray())); + + /* 获取队列的长度 */ + int size = queue.Count(); + Console.WriteLine("队列长度 size = " + size); + + /* 判断队列是否为空 */ + bool isEmpty = queue.Count() == 0; + Console.WriteLine("队列是否为空 = " + isEmpty); + } + } + +} diff --git a/codes/csharp/chapter_stack_and_queue/stack.cs b/codes/csharp/chapter_stack_and_queue/stack.cs new file mode 100644 index 000000000..7be108753 --- /dev/null +++ b/codes/csharp/chapter_stack_and_queue/stack.cs @@ -0,0 +1,40 @@ + +using NUnit.Framework; +using System.Collections.Immutable; + +namespace hello_algo.chapter_stack_and_queue +{ + public class stack + { + [Test] + public void Test() + { + /* 初始化栈 */ + Stack stack = new(); + + /* 元素入栈 */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + Console.WriteLine("栈 stack = " + String.Join(",", stack.ToImmutableArray())); + + /* 访问栈顶元素 */ + int peek = stack.Peek(); + Console.WriteLine("栈顶元素 peek = " + peek); + + /* 元素出栈 */ + int pop = stack.Pop(); + Console.WriteLine("出栈元素 pop = " + pop + ",出栈后 stack = " + String.Join(",", stack.ToImmutableArray())); + + /* 获取栈的长度 */ + int size = stack.Count(); + Console.WriteLine("栈的长度 size = " + size); + + /* 判断是否为空 */ + bool isEmpty = stack.Count() == 0; + Console.WriteLine("栈是否为空 = " + isEmpty); + } + } +} \ No newline at end of file diff --git a/codes/csharp/chapter_tree/avl_tree.cs b/codes/csharp/chapter_tree/avl_tree.cs new file mode 100644 index 000000000..8d6a282b7 --- /dev/null +++ b/codes/csharp/chapter_tree/avl_tree.cs @@ -0,0 +1,264 @@ +using hello_algo.include; +using NUnit.Framework; +using System.Collections.Generic; + +namespace hello_algo.chapter_tree +{ + // Tree class + class AVLTree + { + public TreeNode? root; // 根节点 + + /* 获取结点高度 */ + public int height(TreeNode? node) + { + // 空结点高度为 -1 ,叶结点高度为 0 + return node == null ? -1 : node.height; + } + + /* 更新结点高度 */ + private void updateHeight(TreeNode node) + { + // 结点高度等于最高子树高度 + 1 + node.height = Math.Max(height(node.left), height(node.right)) + 1; + } + + /* 获取平衡因子 */ + public int balanceFactor(TreeNode? node) + { + // 空结点平衡因子为 0 + if (node == null) return 0; + // 结点平衡因子 = 左子树高度 - 右子树高度 + return height(node.left) - height(node.right); + } + + /* 右旋操作 */ + TreeNode? rightRotate(TreeNode? node) + { + if (node == null) + return null; + + TreeNode? child = node.left; + TreeNode? grandChild = child?.right; + // 以 child 为原点,将 node 向右旋转 + child.right = node; + node.left = grandChild; + // 更新结点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + + /* 左旋操作 */ + TreeNode? leftRotate(TreeNode? node) + { + if (node == null) + return null; + + TreeNode? child = node.right; + TreeNode? grandChild = child?.left; + // 以 child 为原点,将 node 向左旋转 + child.left = node; + node.right = grandChild; + // 更新结点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } + + /* 执行旋转操作,使该子树重新恢复平衡 */ + private TreeNode? rotate(TreeNode? node) + { + if (node == null) + return node; + + // 获取结点 node 的平衡因子 + int balanceFactorInt = balanceFactor(node); + // 左偏树 + if (balanceFactorInt > 1) + { + if (balanceFactor(node.left) >= 0) + { + // 右旋 + return rightRotate(node); + } + else + { + // 先左旋后右旋 + node.left = leftRotate(node?.left); + return rightRotate(node); + } + } + // 右偏树 + if (balanceFactorInt < -1) + { + if (balanceFactor(node.right) <= 0) + { + // 左旋 + return leftRotate(node); + } + else + { + // 先右旋后左旋 + node.right = rightRotate(node?.right); + return leftRotate(node); + } + } + // 平衡树,无需旋转,直接返回 + return node; + } + + /* 插入结点 */ + public TreeNode? insert(int val) + { + root = insertHelper(root, val); + return root; + } + + /* 递归插入结点(辅助函数) */ + private TreeNode? insertHelper(TreeNode? node, int val) + { + if (node == null) return new TreeNode(val); + /* 1. 查找插入位置,并插入结点 */ + if (val < node.val) + node.left = insertHelper(node.left, val); + else if (val > node.val) + node.right = insertHelper(node.right, val); + else + return node; // 重复结点不插入,直接返回 + updateHeight(node); // 更新结点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } + + /* 删除结点 */ + public TreeNode? remove(int val) + { + root = removeHelper(root, val); + return root; + } + + /* 递归删除结点(辅助函数) */ + private TreeNode? removeHelper(TreeNode? node, int? val) + { + if (node == null) return null; + /* 1. 查找结点,并删除之 */ + if (val < node.val) + node.left = removeHelper(node.left, val); + else if (val > node.val) + node.right = removeHelper(node.right, val); + else + { + if (node.left == null || node.right == null) + { + TreeNode? child = node.left != null ? node.left : node.right; + // 子结点数量 = 0 ,直接删除 node 并返回 + if (child == null) + return null; + // 子结点数量 = 1 ,直接删除 node + else + node = child; + } + else + { + // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 + TreeNode? temp = minNode(node.right); + node.right = removeHelper(node.right, temp?.val); + node.val = temp?.val; + } + } + updateHeight(node); // 更新结点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } + + /* 获取最小结点 */ + private TreeNode? minNode(TreeNode? node) + { + if (node == null) return node; + // 循环访问左子结点,直到叶结点时为最小结点,跳出 + while (node.left != null) + { + node = node.left; + } + return node; + } + + /* 查找结点 */ + public TreeNode? search(int val) + { + TreeNode? cur = root; + // 循环查找,越过叶结点后跳出 + while (cur != null) + { + // 目标结点在 root 的右子树中 + if (cur.val < val) + cur = cur.right; + // 目标结点在 root 的左子树中 + else if (cur.val > val) + cur = cur.left; + // 找到目标结点,跳出循环 + else + break; + } + // 返回目标结点 + return cur; + } + } + + public class avl_tree + { + static void testInsert(AVLTree tree, int val) + { + tree.insert(val); + Console.WriteLine("\n插入结点 " + val + " 后,AVL 树为"); + PrintUtil.printTree(tree.root); + } + + static void testRemove(AVLTree tree, int val) + { + tree.remove(val); + Console.WriteLine("\n删除结点 " + val + " 后,AVL 树为"); + PrintUtil.printTree(tree.root); + } + + [Test] + public void Test() + { + /* 初始化空 AVL 树 */ + AVLTree avlTree = new AVLTree(); + + /* 插入结点 */ + // 请关注插入结点后,AVL 树是如何保持平衡的 + testInsert(avlTree, 1); + testInsert(avlTree, 2); + testInsert(avlTree, 3); + testInsert(avlTree, 4); + testInsert(avlTree, 5); + testInsert(avlTree, 8); + testInsert(avlTree, 7); + testInsert(avlTree, 9); + testInsert(avlTree, 10); + testInsert(avlTree, 6); + + /* 插入重复结点 */ + testInsert(avlTree, 7); + + /* 删除结点 */ + // 请关注删除结点后,AVL 树是如何保持平衡的 + testRemove(avlTree, 8); // 删除度为 0 的结点 + testRemove(avlTree, 5); // 删除度为 1 的结点 + testRemove(avlTree, 4); // 删除度为 2 的结点 + + /* 查询结点 */ + TreeNode? node = avlTree.search(7); + Console.WriteLine("\n查找到的结点对象为 " + node + ",结点值 = " + node?.val); + } + } +} diff --git a/codes/csharp/chapter_tree/binary_search_tree.cs b/codes/csharp/chapter_tree/binary_search_tree.cs new file mode 100644 index 000000000..9d52d000f --- /dev/null +++ b/codes/csharp/chapter_tree/binary_search_tree.cs @@ -0,0 +1,181 @@ +using hello_algo.include; +using NUnit.Framework; +using System.Collections.Generic; + + +namespace hello_algo.chapter_tree +{ + internal class binary_search_tree + { + TreeNode? root; + + /// + /// 查找结点 + /// + /// + /// + TreeNode? search(int num) + { + TreeNode? cur = root; + // 循环查找,越过叶结点后跳出 + while (cur != null) + { + // 目标结点在 root 的右子树中 + if (cur.val < num) cur = cur.right; + // 目标结点在 root 的左子树中 + else if (cur.val > num) cur = cur.left; + // 找到目标结点,跳出循环 + else break; + } + // 返回目标结点 + return cur; + } + + /// + /// 插入结点 + /// + /// + /// + TreeNode? insert(int num) + { + // 若树为空,直接提前返回 + if (root == null) return null; + TreeNode? cur = root, pre = null; + // 循环查找,越过叶结点后跳出 + while (cur != null) + { + // 找到重复结点,直接返回 + if (cur.val == num) return null; + pre = cur; + // 插入位置在 root 的右子树中 + if (cur.val < num) cur = cur.right; + // 插入位置在 root 的左子树中 + else cur = cur.left; + } + + // 插入结点 val + TreeNode node = new TreeNode(num); + if (pre != null) + { + if (pre.val < num) pre.right = node; + else pre.left = node; + } + return node; + } + + + /* 删除结点 */ + TreeNode? remove(int? num) + { + // 若树为空,直接提前返回 + if (root == null) return null; + TreeNode? cur = root, pre = null; + // 循环查找,越过叶结点后跳出 + while (cur != null) + { + // 找到待删除结点,跳出循环 + if (cur.val == num) break; + pre = cur; + // 待删除结点在 root 的右子树中 + if (cur.val < num) cur = cur.right; + // 待删除结点在 root 的左子树中 + else cur = cur.left; + } + // 若无待删除结点,则直接返回 + if (cur == null || pre == null) return null; + // 子结点数量 = 0 or 1 + if (cur.left == null || cur.right == null) + { + // 当子结点数量 = 0 / 1 时, child = null / 该子结点 + TreeNode? child = cur.left != null ? cur.left : cur.right; + // 删除结点 cur + if (pre.left == cur) + { + pre.left = child; + } + else + { + pre.right = child; + } + + } + // 子结点数量 = 2 + else + { + // 获取中序遍历中 cur 的下一个结点 + TreeNode? nex = min(cur.right); + if (nex != null) + { + int? tmp = nex.val; + // 递归删除结点 nex + remove(nex.val); + // 将 nex 的值复制给 cur + cur.val = tmp; + } + } + return cur; + } + + /* 获取最小结点 */ + TreeNode? min(TreeNode? root) + { + if (root == null) return root; + // 循环访问左子结点,直到叶结点时为最小结点,跳出 + while (root.left != null) + { + root = root.left; + } + return root; + } + + [Test] + public void Test() + { + // 初始化结点 + TreeNode n1 = new TreeNode(1); + TreeNode n2 = new TreeNode(2); + TreeNode n3 = new TreeNode(3); + TreeNode n4 = new TreeNode(4); + TreeNode n5 = new TreeNode(5); + TreeNode n6 = new TreeNode(6); + TreeNode n7 = new TreeNode(7); + TreeNode n8 = new TreeNode(8); + TreeNode n9 = new TreeNode(9); + TreeNode n10 = new TreeNode(10); + TreeNode n11 = new TreeNode(11); + TreeNode n12 = new TreeNode(12); + TreeNode n13 = new TreeNode(13); + TreeNode n14 = new TreeNode(14); + TreeNode n15 = new TreeNode(15); + root = n8; + root.left = n4; + root.right = n12; + n4.left = n2; + n4.right = n6; + n12.left = n10; + n12.right = n14; + n2.left = n1; + n2.right = n3; + n6.left = n5; + n6.right = n7; + n10.left = n9; + n10.right = n11; + n14.left = n13; + n14.right = n15; + + var cur = search(7); + Console.WriteLine("查找结点 结果 = " + cur?.val); + + var node = insert(16); + Console.WriteLine("插入结点 结果 = " + node?.val); + + node = remove(1); + Console.WriteLine("删除结点 结果 = " + node?.val); + node = remove(2); + Console.WriteLine("删除结点 结果 = " + node?.val); + node = remove(4); + Console.WriteLine("删除结点 结果 = " + node?.val); + + } + } +} diff --git a/codes/csharp/chapter_tree/binary_tree.cs b/codes/csharp/chapter_tree/binary_tree.cs new file mode 100644 index 000000000..d3893e79d --- /dev/null +++ b/codes/csharp/chapter_tree/binary_tree.cs @@ -0,0 +1,42 @@ +using hello_algo.include; +using NUnit.Framework; +using System.Collections.Generic; + + +namespace hello_algo.chapter_tree +{ + + public class binary_tree + { + [Test] + public void Test() + { + /* 初始化二叉树 */ + // 初始化结点 + TreeNode n1 = new TreeNode(1); + TreeNode n2 = new TreeNode(2); + TreeNode n3 = new TreeNode(3); + TreeNode n4 = new TreeNode(4); + TreeNode n5 = new TreeNode(5); + // 构建引用指向(即指针) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; + Console.WriteLine("\n初始化二叉树\n"); + PrintUtil.printTree(n1); + + /* 插入与删除结点 */ + TreeNode P = new TreeNode(0); + // 在 n1 -> n2 中间插入结点 P + n1.left = P; + P.left = n2; + Console.WriteLine("\n插入结点 P 后\n"); + PrintUtil.printTree(n1); + // 删除结点 P + n1.left = n2; + Console.WriteLine("\n删除结点 P 后\n"); + PrintUtil.printTree(n1); + } + } +} \ No newline at end of file diff --git a/codes/csharp/chapter_tree/binary_tree_bfs.cs b/codes/csharp/chapter_tree/binary_tree_bfs.cs new file mode 100644 index 000000000..2715783a8 --- /dev/null +++ b/codes/csharp/chapter_tree/binary_tree_bfs.cs @@ -0,0 +1,117 @@ +using hello_algo.include; +using NUnit.Framework; + +namespace hello_algo.chapter_tree +{ + public class binary_tree_bfs + { + + /// + /// 层序遍历 + /// + /// + /// + public List hierOrder(TreeNode root) + { + // 初始化队列,加入根结点 + Queue queue = new(); + queue.Enqueue(root); + // 初始化一个列表,用于保存遍历序列 + List list = new(); + while (queue.Count != 0) + { + TreeNode node = queue.Dequeue(); // 队列出队 + list.Add(node.val); // 保存结点值 + if (node.left != null) + queue.Enqueue(node.left); // 左子结点入队 + if (node.right != null) + queue.Enqueue(node.right); // 右子结点入队 + } + return list; + } + + List list = new(); + + /// + /// 前序遍历 + /// + /// + void preOrder(TreeNode? root) + { + if (root == null) return; + // 访问优先级:根结点 -> 左子树 -> 右子树 + list.Add(root.val); + preOrder(root.left); + preOrder(root.right); + } + + /// + /// 中序遍历 + /// + /// + void inOrder(TreeNode? root) + { + if (root == null) return; + // 访问优先级:左子树 -> 根结点 -> 右子树 + inOrder(root.left); + list.Add(root.val); + inOrder(root.right); + } + + /// + /// 后序遍历 + /// + /// + void postOrder(TreeNode? root) + { + if (root == null) return; + // 访问优先级:左子树 -> 右子树 -> 根结点 + postOrder(root.left); + postOrder(root.right); + list.Add(root.val); + } + + /// + /// 辅助函数,数组转字符串 + /// + public static string ToString(int?[] nums) + { + return string.Join(",", nums); + } + + [Test] + public void Test() + { + // 初始化结点 + TreeNode root = new TreeNode(1); + TreeNode n2 = new TreeNode(2); + TreeNode n3 = new TreeNode(3); + TreeNode n4 = new TreeNode(4); + TreeNode n5 = new TreeNode(5); + TreeNode n6 = new TreeNode(6); + TreeNode n7 = new TreeNode(7); + // 构建引用指向(即指针) + root.left = n2; + root.right = n3; + n2.left = n4; + n2.right = n5; + n3.left = n6; + n3.right = n7; + + list = hierOrder(root); + Console.WriteLine("层序遍历 结果 = " + ToString(list.ToArray())); + + list = new List(); + preOrder(root); + Console.WriteLine("前序遍历 结果 = " + ToString(list.ToArray())); + + list = new List(); + inOrder(root); + Console.WriteLine("中序遍历 结果 = " + ToString(list.ToArray())); + + list = new List(); + postOrder(root); + Console.WriteLine("后序遍历 结果 = " + ToString(list.ToArray())); + } + } +} diff --git a/codes/csharp/include/PrintUtil.cs b/codes/csharp/include/PrintUtil.cs new file mode 100644 index 000000000..bdd436ea3 --- /dev/null +++ b/codes/csharp/include/PrintUtil.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.SymbolStore; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace hello_algo.include +{ + public class Trunk + { + public Trunk? prev; + public String str; + + public Trunk(Trunk? prev, String str) + { + this.prev = prev; + this.str = str; + } + }; + + public class PrintUtil + { + /** + * Print a linked list + * @param head + */ + public static void printLinkedList(ListNode head) + { + List list = new(); + while (head != null) + { + list.Add(head.val.ToString()); + head = head.next; + } + Console.Write(String.Join(" -> ", list)); + } + + /** + * The interface of the tree printer + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + * @param root + */ + public static void printTree(TreeNode? root) + { + printTree(root, null, false); + } + + /** + * Print a binary tree + * @param root + * @param prev + * @param isLeft + */ + public static void printTree(TreeNode? root, Trunk? prev, bool isLeft) + { + if (root == null) + { + return; + } + + String prev_str = " "; + Trunk trunk = new Trunk(prev, prev_str); + + printTree(root.right, trunk, true); + + if (prev == null) + { + trunk.str = "———"; + } + else if (isLeft) + { + trunk.str = "/———"; + prev_str = " |"; + } + else + { + trunk.str = "\\———"; + prev.str = prev_str; + } + + showTrunks(trunk); + Console.Write(" " + root.val); + + if (prev != null) + { + prev.str = prev_str; + } + trunk.str = " |"; + + printTree(root.left, trunk, false); + } + + /** + * Helper function to print branches of the binary tree + * @param p + */ + public static void showTrunks(Trunk? p) + { + if (p == null) + { + return; + } + + showTrunks(p.prev); + Console.Write(p.str); + } + + /** + * Print a hash map + * @param + * @param + * @param map + */ + public static void printHashMap(Dictionary map) where K : notnull + { + foreach (var kv in map.Keys) + { + Console.WriteLine(kv.ToString() + " -> " + map[kv]?.ToString()); + } + } + } + +} diff --git a/codes/csharp/include/TreeNode.cs b/codes/csharp/include/TreeNode.cs new file mode 100644 index 000000000..d9130b226 --- /dev/null +++ b/codes/csharp/include/TreeNode.cs @@ -0,0 +1,92 @@ +namespace hello_algo.include +{ + public class TreeNode + { + public int? val; // 结点值 + public int height; // 结点高度 + public TreeNode? left; // 左子结点引用 + public TreeNode? right; // 右子结点引用 + + public TreeNode(int? x) + { + val = x; + } + + /** + * Generate a binary tree with an array + * @param arr + * @return + */ + public static TreeNode? arrToTree(int?[] arr) + { + if (arr.Length == 0) + return null; + + TreeNode root = new TreeNode(arr[0]); + Queue queue = new Queue(); + queue.Enqueue(root); + int i = 1; + while (queue.Count!=0) + { + TreeNode node = queue.Dequeue(); + if (arr[i] != null) + { + node.left = new TreeNode(arr[i]); + queue.Enqueue(node.left); + } + i++; + if (arr[i] != null) + { + node.right = new TreeNode(arr[i]); + queue.Enqueue(node.right); + } + i++; + } + return root; + } + + /** + * Serialize a binary tree to a list + * @param root + * @return + */ + public static List treeToList(TreeNode root) + { + List list = new(); + if (root == null) return list; + Queue queue = new(); + while (queue.Count != 0) + { + TreeNode? node = queue.Dequeue(); + if (node != null) + { + list.Add(node.val); + queue.Enqueue(node.left); + queue.Enqueue(node.right); + } + else + { + list.Add(null); + } + } + return list; + } + + /** + * Get a tree node with specific value in a binary tree + * @param root + * @param val + * @return + */ + public static TreeNode? getTreeNode(TreeNode? root, int val) + { + if (root == null) + return null; + if (root.val == val) + return root; + TreeNode? left = getTreeNode(root.left, val); + TreeNode? right = getTreeNode(root.right, val); + return left != null ? left : right; + } + } +} diff --git a/docs/chapter_array_and_linkedlist/array.md b/docs/chapter_array_and_linkedlist/array.md index 2ad57f0f3..3228e678f 100644 --- a/docs/chapter_array_and_linkedlist/array.md +++ b/docs/chapter_array_and_linkedlist/array.md @@ -74,9 +74,6 @@ comments: true /* 初始化数组 */ int[] arr = new int[5]; // { 0, 0, 0, 0, 0 } int[] nums = { 1, 3, 2, 5, 4 }; - - var arr2=new int[5]; // { 0, 0, 0, 0, 0 } - var nums2=new int[]{1,2,3,4,5}; ``` ## 数组优点 diff --git a/docs/chapter_array_and_linkedlist/linked_list.md b/docs/chapter_array_and_linkedlist/linked_list.md index 412d09e79..afff4b13f 100644 --- a/docs/chapter_array_and_linkedlist/linked_list.md +++ b/docs/chapter_array_and_linkedlist/linked_list.md @@ -208,13 +208,13 @@ comments: true === "C#" ```csharp title="" - // 初始化链表 1 -> 3 -> 2 -> 5 -> 4 - // 初始化各结点 - n0 = new ListNode(1); - n1 = new ListNode(3); - n2 = new ListNode(2); - n3 = new ListNode(5); - n4 = new ListNode(4); + /* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */ + // 初始化各个结点 + ListNode n0 = new ListNode(1); + ListNode n1 = new ListNode(3); + ListNode n2 = new ListNode(2); + ListNode n3 = new ListNode(5); + ListNode n4 = new ListNode(4); // 构建引用指向 n0.next = n1; n1.next = n2; diff --git a/docs/chapter_array_and_linkedlist/list.md b/docs/chapter_array_and_linkedlist/list.md index 2098a82c1..505265b11 100644 --- a/docs/chapter_array_and_linkedlist/list.md +++ b/docs/chapter_array_and_linkedlist/list.md @@ -83,7 +83,12 @@ comments: true === "C#" ```csharp title="list.cs" - + /* 初始化列表 */ + // 无初始值 + List list1 = new (); + // 有初始值(注意数组的元素类型需为 int[] 的包装类 Integer[]) + int[] numbers = new int[] { 1, 3, 2, 5, 4 }; + List list = numbers.ToList(); ``` **访问与更新元素。** 列表的底层数据结构是数组,因此可以在 $O(1)$ 时间内访问与更新元素,效率很高。 @@ -157,7 +162,11 @@ comments: true === "C#" ```csharp title="list.cs" + /* 访问元素 */ + int num = list[1]; + /* 更新元素 */ + list[1]=0; ``` **在列表中添加、插入、删除元素。** 相对于数组,列表可以自由地添加与删除元素。在列表尾部添加元素的时间复杂度为 $O(1)$ ,但是插入与删除元素的效率仍与数组一样低,时间复杂度为 $O(N)$ 。 @@ -291,7 +300,21 @@ comments: true === "C#" ```csharp title="list.cs" + /* 清空列表 */ + list.Clear(); + /* 尾部添加元素 */ + list.Add(1); + list.Add(3); + list.Add(2); + list.Add(5); + list.Add(4); + + /* 中间插入元素 */ + list.Insert(3, 6); + + /* 删除元素 */ + list.Remove(3); ``` **遍历列表。** 与数组一样,列表可以使用索引遍历,也可以使用 `for-each` 直接遍历。 @@ -399,7 +422,19 @@ comments: true === "C#" ```csharp title="list.cs" + /* 通过索引遍历列表 */ + int count = 0; + for (int i = 0; i < list.Count(); i++) + { + count++; + } + /* 直接遍历列表元素 */ + count = 0; + foreach (int n in list) + { + count++; + } ``` **拼接两个列表。** 再创建一个新列表 `list1` ,我们可以将其中一个列表拼接到另一个的尾部。 @@ -462,7 +497,9 @@ comments: true === "C#" ```csharp title="list.cs" - + /* 拼接两个列表 */ + List list1 = new() { 6, 8, 7, 10, 9 }; + list.AddRange(list1); ``` **排序列表。** 排序也是常用的方法之一,完成列表排序后,我们就可以使用在数组类算法题中经常考察的「二分查找」和「双指针」算法了。 @@ -518,7 +555,8 @@ comments: true === "C#" ```csharp title="list.cs" - + /* 排序列表 */ + list.Sort(); // 排序后,列表元素从小到大排列 ``` ## 列表简易实现 * @@ -1084,5 +1122,114 @@ comments: true === "C#" ```csharp title="my_list.cs" + class MyList + { + private int[] nums; // 数组(存储列表元素) + private int capacity = 10; // 列表容量 + private int size = 0; // 列表长度(即当前元素数量) + private int extendRatio = 2; // 每次列表扩容的倍数 + /* 构造函数 */ + public MyList() + { + nums = new int[capacity]; + } + + /* 获取列表长度(即当前元素数量)*/ + public int Size() + { + return size; + } + + /* 获取列表容量 */ + public int Capacity() + { + return capacity; + } + + /* 访问元素 */ + public int get(int index) + { + // 索引如果越界则抛出异常,下同 + if (index >= size) + throw new IndexOutOfRangeException("索引越界"); + return nums[index]; + } + + /* 更新元素 */ + public void set(int index, int num) + { + if (index >= size) + throw new IndexOutOfRangeException("索引越界"); + nums[index] = num; + } + + /* 尾部添加元素 */ + public void add(int num) + { + // 元素数量超出容量时,触发扩容机制 + if (size == Capacity()) + extendCapacity(); + nums[size] = num; + // 更新元素数量 + size++; + } + + /* 中间插入元素 */ + public void insert(int index, int num) + { + if (index >= size) + throw new IndexOutOfRangeException("索引越界"); + // 元素数量超出容量时,触发扩容机制 + if (size == Capacity()) + extendCapacity(); + // 将索引 index 以及之后的元素都向后移动一位 + for (int j = size - 1; j >= index; j--) + { + nums[j + 1] = nums[j]; + } + nums[index] = num; + // 更新元素数量 + size++; + } + + /* 删除元素 */ + public int remove(int index) + { + if (index >= size) + throw new IndexOutOfRangeException("索引越界"); + int num = nums[index]; + // 将索引 index 之后的元素都向前移动一位 + for (int j = index; j < size - 1; j++) + { + nums[j] = nums[j + 1]; + } + // 更新元素数量 + size--; + // 返回被删除元素 + return num; + } + + /* 列表扩容 */ + public void extendCapacity() + { + // 新建一个长度为 size 的数组,并将原数组拷贝到新数组 + System.Array.Resize(ref nums, Capacity() * extendRatio); + // 更新列表容量 + capacity = nums.Length; + } + + /* 将列表转换为数组 */ + public int[] toArray() + { + int size = Size(); + // 仅转换有效长度范围内的列表元素 + int[] nums = new int[size]; + for (int i = 0; i < size; i++) + { + nums[i] = get(i); + } + return nums; + } + } ``` diff --git a/docs/chapter_computational_complexity/space_complexity.md b/docs/chapter_computational_complexity/space_complexity.md index d640c501b..8ea15a2e8 100644 --- a/docs/chapter_computational_complexity/space_complexity.md +++ b/docs/chapter_computational_complexity/space_complexity.md @@ -149,7 +149,29 @@ comments: true === "C#" ```csharp title="" + /* 类 */ + class Node + { + int val; + Node next; + Node(int x) { val = x; } + } + /* 函数(或称方法) */ + int function() + { + // do something... + return 0; + } + + int algorithm(int n) + { // 输入数据 + int a = 0; // 暂存数据(常量) + int b = 0; // 暂存数据(变量) + Node node = new Node(0); // 暂存数据(对象) + int c = function(); // 栈帧空间(调用函数) + return a + b + c; // 输出数据 + } ``` ## 推算方法 @@ -228,7 +250,15 @@ comments: true === "C#" ```csharp title="" - + void algorithm(int n) + { + int a = 0; // O(1) + int[] b = new int[10000]; // O(1) + if (n > 10) + { + int[] nums = new int[n]; // O(n) + } + } ``` **在递归函数中,需要注意统计栈帧空间。** 例如函数 `loop()`,在循环中调用了 $n$ 次 `function()` ,每轮中的 `function()` 都返回并释放了栈帧空间,因此空间复杂度仍为 $O(1)$ 。而递归函数 `recur()` 在运行中会同时存在 $n$ 个未返回的 `recur()` ,从而使用 $O(n)$ 的栈帧空间。 @@ -330,13 +360,31 @@ comments: true === "C" ```c title="" - + ``` === "C#" ```csharp title="" - + int function() + { + // do something + return 0; + } + /* 循环 O(1) */ + void loop(int n) + { + for (int i = 0; i < n; i++) + { + function(); + } + } + /* 递归 O(n) */ + int recur(int n) + { + if (n == 1) return 1; + return recur(n - 1); + } ``` ## 常见类型 @@ -467,7 +515,25 @@ $$ === "C#" ```csharp title="space_complexity.cs" - + /* 常数阶 */ + void constant(int n) + { + // 常量、变量、对象占用 O(1) 空间 + int a = 0; + int b = 0; + int[] nums = new int[10000]; + ListNode node = new ListNode(0); + // 循环中的变量占用 O(1) 空间 + for (int i = 0; i < n; i++) + { + int c = 0; + } + // 循环中的函数占用 O(1) 空间 + for (int i = 0; i < n; i++) + { + function(); + } + } ``` ### 线性阶 $O(n)$ @@ -568,7 +634,24 @@ $$ === "C#" ```csharp title="space_complexity.cs" - + /* 线性阶 */ + void linear(int n) + { + // 长度为 n 的数组占用 O(n) 空间 + int[] nums = new int[n]; + // 长度为 n 的列表占用 O(n) 空间 + List nodes = new(); + for (int i = 0; i < n; i++) + { + nodes.Add(new ListNode(i)); + } + // 长度为 n 的哈希表占用 O(n) 空间 + Dictionary map = new(); + for (int i = 0; i < n; i++) + { + map.Add(i, i.ToString()); + } + } ``` 以下递归函数会同时存在 $n$ 个未返回的 `algorithm()` 函数,使用 $O(n)$ 大小的栈帧空间。 @@ -639,7 +722,13 @@ $$ === "C#" ```csharp title="space_complexity.cs" - + /* 线性阶(递归实现) */ + void linearRecur(int n) + { + Console.WriteLine("递归 n = " + n); + if (n == 1) return; + linearRecur(n - 1); + } ``` ![space_complexity_recursive_linear](space_complexity.assets/space_complexity_recursive_linear.png) @@ -729,6 +818,23 @@ $$ === "C#" ```csharp title="space_complexity.cs" + /* 平方阶 */ + void quadratic(int n) + { + // 矩阵占用 O(n^2) 空间 + int[,] numMatrix = new int[n, n]; + // 二维列表占用 O(n^2) 空间 + List> numList = new(); + for (int i = 0; i < n; i++) + { + List tmp = new(); + for (int j = 0; j < n; j++) + { + tmp.Add(0); + } + numList.Add(tmp); + } + } ``` @@ -804,6 +910,14 @@ $$ === "C#" ```csharp title="space_complexity.cs" + /* 平方阶(递归实现) */ + int quadraticRecur(int n) + { + if (n <= 0) return 0; + int[] nums = new int[n]; + Console.WriteLine("递归 n = " + n + " 中的 nums 长度 = " + nums.Length); + return quadraticRecur(n - 1); + } ``` @@ -889,7 +1003,15 @@ $$ === "C#" ```csharp title="space_complexity.cs" - + /* 指数阶(建立满二叉树) */ + TreeNode? buildTree(int n) + { + if (n == 0) return null; + TreeNode root = new TreeNode(0); + root.left = buildTree(n - 1); + root.right = buildTree(n - 1); + return root; + } ``` ![space_complexity_exponential](space_complexity.assets/space_complexity_exponential.png) diff --git a/docs/chapter_computational_complexity/space_time_tradeoff.md b/docs/chapter_computational_complexity/space_time_tradeoff.md index 23c2ae7a6..651aff148 100644 --- a/docs/chapter_computational_complexity/space_time_tradeoff.md +++ b/docs/chapter_computational_complexity/space_time_tradeoff.md @@ -130,7 +130,23 @@ comments: true === "C#" ```csharp title="leetcode_two_sum.cs" - + class SolutionBruteForce + { + public int[] twoSum(int[] nums, int target) + { + int size = nums.Length; + // 两层循环,时间复杂度 O(n^2) + for (int i = 0; i < size - 1; i++) + { + for (int j = i + 1; j < size; j++) + { + if (nums[i] + nums[j] == target) + return new int[] { i, j }; + } + } + return new int[0]; + } + } ``` ### 方法二:辅助哈希表 @@ -258,5 +274,23 @@ comments: true === "C#" ```csharp title="leetcode_two_sum.cs" - + class SolutionHashMap + { + public int[] twoSum(int[] nums, int target) + { + int size = nums.Length; + // 辅助哈希表,空间复杂度 O(n) + Dictionary dic = new(); + // 单层循环,时间复杂度 O(n) + for (int i = 0; i < size; i++) + { + if (dic.ContainsKey(target - nums[i])) + { + return new int[] { dic[target - nums[i]], i }; + } + dic.Add(nums[i], i); + } + return new int[0]; + } + } ``` diff --git a/docs/chapter_computational_complexity/time_complexity.md b/docs/chapter_computational_complexity/time_complexity.md index 0313c533b..cdd444eb6 100644 --- a/docs/chapter_computational_complexity/time_complexity.md +++ b/docs/chapter_computational_complexity/time_complexity.md @@ -97,7 +97,18 @@ $$ === "C#" ```csharp title="" - + // 在某运行平台下 + void algorithm(int n) + { + int a = 2; // 1 ns + a = a + 1; // 1 ns + a = a * 2; // 10 ns + // 循环 n 次 + for (int i = 0; i < n; i++) + { // 1 ns ,每轮都要执行 i++ + Console.WriteLine(0); // 5 ns + } + } ``` 但实际上, **统计算法的运行时间既不合理也不现实。** 首先,我们不希望预估时间和运行平台绑定,毕竟算法需要跑在各式各样的平台之上。其次,我们很难获知每一种操作的运行时间,这为预估过程带来了极大的难度。 @@ -212,7 +223,27 @@ $$ === "C#" ```csharp title="" - + // 算法 A 时间复杂度:常数阶 + void algorithm_A(int n) + { + Console.WriteLine(0); + } + // 算法 B 时间复杂度:线性阶 + void algorithm_B(int n) + { + for (int i = 0; i < n; i++) + { + Console.WriteLine(0); + } + } + // 算法 C 时间复杂度:常数阶 + void algorithm_C(int n) + { + for (int i = 0; i < 1000000; i++) + { + Console.WriteLine(0); + } + } ``` ![time_complexity_first_example](time_complexity.assets/time_complexity_first_example.png) @@ -310,7 +341,15 @@ $$ === "C#" ```csharp title="" - + void algorithm(int n) { + int a = 1; // +1 + a = a + 1; // +1 + a = a * 2; // +1 + // 循环 n 次 + for (int i = 0; i < n; i++) { // +1(每轮都执行 i ++) + Console.WriteLine(0); // +1 + } + } ``` $T(n)$ 是个一次函数,说明时间增长趋势是线性的,因此易得时间复杂度是线性阶。 @@ -457,7 +496,24 @@ $$ === "C#" ```csharp title="" - + void algorithm(int n) + { + int a = 1; // +0(技巧 1) + a = a + n; // +0(技巧 1) + // +n(技巧 2) + for (int i = 0; i < 5 * n + 1; i++) + { + Console.WriteLine(0); + } + // +n*n(技巧 3) + for (int i = 0; i < 2 * n; i++) + { + for (int j = 0; j < n + 1; j++) + { + Console.WriteLine(0); + } + } + } ``` ### 2. 判断渐近上界 @@ -576,7 +632,15 @@ $$ === "C#" ```csharp title="time_complexity.cs" - + /* 常数阶 */ + int constant(int n) + { + int count = 0; + int size = 100000; + for (int i = 0; i < size; i++) + count++; + return count; + } ``` ### 线性阶 $O(n)$ @@ -652,7 +716,14 @@ $$ === "C#" ```csharp title="time_complexity.cs" - + /* 线性阶 */ + int linear(int n) + { + int count = 0; + for (int i = 0; i < n; i++) + count++; + return count; + } ``` 「遍历数组」和「遍历链表」等操作,时间复杂度都为 $O(n)$ ,其中 $n$ 为数组或链表的长度。 @@ -736,7 +807,17 @@ $$ === "C#" ```csharp title="time_complexity.cs" - + /* 线性阶(遍历数组) */ + int arrayTraversal(int[] nums) + { + int count = 0; + // 循环次数与数组长度成正比 + foreach(int num in nums) + { + count++; + } + return count; + } ``` ### 平方阶 $O(n^2)$ @@ -825,7 +906,20 @@ $$ === "C#" ```csharp title="time_complexity.cs" - + /* 平方阶 */ + int quadratic(int n) + { + int count = 0; + // 循环次数与数组长度成平方关系 + for (int i = 0; i < n; i++) + { + for (int j = 0; j < n; j++) + { + count++; + } + } + return count; + } ``` ![time_complexity_constant_linear_quadratic](time_complexity.assets/time_complexity_constant_linear_quadratic.png) @@ -947,6 +1041,28 @@ $$ === "C#" ```csharp title="time_complexity.cs" + /* 平方阶(冒泡排序) */ + int bubbleSort(int[] nums) + { + int count = 0; // 计数器 + // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + for (int i = nums.Length - 1; i > 0; i--) + { + // 内循环:冒泡操作 + for (int j = 0; j < i; j++) + { + if (nums[j] > nums[j + 1]) + { + // 交换 nums[j] 与 nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + count += 3; // 元素交换包含 3 个单元操作 + } + } + } + return count; + } ``` @@ -1048,7 +1164,22 @@ $$ === "C#" ```csharp title="time_complexity.cs" - + /* 指数阶(循环实现) */ + int exponential(int n) + { + int count = 0, bas = 1; + // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) + for (int i = 0; i < n; i++) + { + for (int j = 0; j < bas; j++) + { + count++; + } + bas *= 2; + } + // count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count; + } ``` ![time_complexity_exponential](time_complexity.assets/time_complexity_exponential.png) @@ -1119,7 +1250,12 @@ $$ === "C#" ```csharp title="time_complexity.cs" - + /* 指数阶(递归实现) */ + int expRecur(int n) + { + if (n == 1) return 1; + return expRecur(n - 1) + expRecur(n - 1) + 1; + } ``` ### 对数阶 $O(\log n)$ @@ -1205,7 +1341,17 @@ $$ === "C#" ```csharp title="time_complexity.cs" - + /* 对数阶(循环实现) */ + int logarithmic(float n) + { + int count = 0; + while (n > 1) + { + n = n / 2; + count++; + } + return count; + } ``` ![time_complexity_logarithmic](time_complexity.assets/time_complexity_logarithmic.png) @@ -1276,7 +1422,12 @@ $$ === "C#" ```csharp title="time_complexity.cs" - + /* 对数阶(递归实现) */ + int logRecur(float n) + { + if (n <= 1) return 0; + return logRecur(n / 2) + 1; + } ``` ### 线性对数阶 $O(n \log n)$ @@ -1366,7 +1517,18 @@ $$ === "C#" ```csharp title="time_complexity.cs" - + /* 线性对数阶 */ + int linearLogRecur(float n) + { + if (n <= 1) return 1; + int count = linearLogRecur(n / 2) + + linearLogRecur(n / 2); + for (int i = 0; i < n; i++) + { + count++; + } + return count; + } ``` ![time_complexity_logarithmic_linear](time_complexity.assets/time_complexity_logarithmic_linear.png) @@ -1464,7 +1626,18 @@ $$ === "C#" ```csharp title="time_complexity.cs" - + /* 阶乘阶(递归实现) */ + int factorialRecur(int n) + { + if (n == 0) return 1; + int count = 0; + // 从 1 个分裂出 n 个 + for (int i = 0; i < n; i++) + { + count += factorialRecur(n - 1); + } + return count; + } ``` ![time_complexity_factorial](time_complexity.assets/time_complexity_factorial.png) @@ -1485,7 +1658,7 @@ $$ ```java title="worst_best_time_complexity.java" public class worst_best_time_complexity { /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ - static int[] randomNumbers(int n) { + int[] randomNumbers(int n) { Integer[] nums = new Integer[n]; // 生成数组 nums = { 1, 2, 3, ..., n } for (int i = 0; i < n; i++) { @@ -1502,7 +1675,7 @@ $$ } /* 查找数组 nums 中数字 1 所在索引 */ - static int findOne(int[] nums) { + int findOne(int[] nums) { for (int i = 0; i < nums.length; i++) { if (nums[i] == 1) return i; @@ -1511,7 +1684,7 @@ $$ } /* Driver Code */ - public static void main(String[] args) { + public void main(String[] args) { for (int i = 0; i < 10; i++) { int n = 100; int[] nums = randomNumbers(n); @@ -1652,7 +1825,51 @@ $$ === "C#" ```csharp title="worst_best_time_complexity.cs" + /* 生成一个数组,元素为 { 1, 2, ..., n },顺序被打乱 */ + int[] randomNumbers(int n) + { + int[] nums = new int[n]; + // 生成数组 nums = { 1, 2, 3, ..., n } + for (int i = 0; i < n; i++) + { + nums[i] = i + 1; + } + // 随机打乱数组元素 + for (int i = 0; i < nums.Length; i++) + { + var index = new Random().Next(i, nums.Length); + var tmp = nums[i]; + var ran = nums[index]; + nums[i] = ran; + nums[index] = tmp; + } + return nums; + } + + /* 查找数组 nums 中数字 1 所在索引 */ + int findOne(int[] nums) + { + for (int i = 0; i < nums.Length; i++) + { + if (nums[i] == 1) + return i; + } + return -1; + } + + /* Driver Code */ + public void main(String[] args) + { + for (int i = 0; i < 10; i++) + { + int n = 100; + int[] nums = randomNumbers(n); + int index = findOne(nums); + Console.WriteLine("\n数组 [ 1, 2, ..., n ] 被打乱后 = " + string.Join(",", nums)); + Console.WriteLine("数字 1 的索引为 " + index); + } + } ``` !!! tip diff --git a/docs/chapter_hashing/hash_map.md b/docs/chapter_hashing/hash_map.md index dc4f14764..cb439546a 100644 --- a/docs/chapter_hashing/hash_map.md +++ b/docs/chapter_hashing/hash_map.md @@ -150,7 +150,24 @@ comments: true === "C#" ```csharp title="hash_map.cs" + /* 初始化哈希表 */ + Dictionary map = new (); + /* 添加操作 */ + // 在哈希表中添加键值对 (key, value) + map.Add(12836, "小哈"); + map.Add(15937, "小啰"); + map.Add(16750, "小算"); + map.Add(13276, "小法"); + map.Add(10583, "小鸭"); + + /* 查询操作 */ + // 向哈希表输入键 key ,得到值 value + String name = map[15937]; + + /* 删除操作 */ + // 在哈希表中删除键值对 (key, value) + map.Remove(10583); ``` 遍历哈希表有三种方式,即 **遍历键值对、遍历键、遍历值**。 @@ -245,7 +262,19 @@ comments: true === "C#" ```csharp title="hash_map.cs" - + /* 遍历哈希表 */ + // 遍历键值对 Key->Value + foreach (var kv in map) { + Console.WriteLine(kv.Key + " -> " + kv.Value); + } + // 单独遍历键 key + foreach (int key in map.Keys) { + Console.WriteLine(key); + } + // 单独遍历值 value + foreach (String val in map.Values) { + Console.WriteLine(val); + } ``` ## 哈希函数 @@ -489,6 +518,64 @@ $$ === "C#" ```csharp title="array_hash_map.cs" + /* 键值对 int->String */ + class Entry + { + public int key; + public String val; + public Entry(int key, String val) + { + this.key = key; + this.val = val; + } + } + + /* 基于数组简易实现的哈希表 */ + class ArrayHashMap + { + private List bucket; + public ArrayHashMap() + { + // 初始化一个长度为 100 的桶(数组) + bucket = new (); + for (int i = 0; i < 100; i++) + { + bucket.Add(null); + } + } + + /* 哈希函数 */ + private int hashFunc(int key) + { + int index = key % 100; + return index; + } + + /* 查询操作 */ + public String? get(int key) + { + int index = hashFunc(key); + Entry? pair = bucket[index]; + if (pair == null) return null; + return pair.val; + } + + /* 添加操作 */ + public void put(int key, String val) + { + Entry pair = new Entry(key, val); + int index = hashFunc(key); + bucket[index]=pair; + } + + /* 删除操作 */ + public void remove(int key) + { + int index = hashFunc(key); + // 置为 null ,代表删除 + bucket[index]=null; + } + } ``` diff --git a/docs/chapter_searching/binary_search.md b/docs/chapter_searching/binary_search.md index 7e765a67f..bdaaadc5d 100644 --- a/docs/chapter_searching/binary_search.md +++ b/docs/chapter_searching/binary_search.md @@ -173,7 +173,25 @@ $$ === "C#" ```csharp title="binary_search.cs" - + /* 二分查找(双闭区间) */ + int binarySearch(int[] nums, int target) + { + // 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 + int i = 0, j = nums.Length - 1; + // 循环,当搜索区间为空时跳出(当 i > j 时为空) + while (i <= j) + { + int m = (i + j) / 2; // 计算中点索引 m + if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j] 中 + i = m + 1; + else if (nums[m] > target) // 此情况说明 target 在区间 [i, m-1] 中 + j = m - 1; + else // 找到目标元素,返回其索引 + return m; + } + // 未找到目标元素,返回 -1 + return -1; + } ``` ### “左闭右开”实现 @@ -303,7 +321,25 @@ $$ === "C#" ```csharp title="binary_search.cs" - + /* 二分查找(左闭右开) */ + int binarySearch1(int[] nums, int target) + { + // 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 + int i = 0, j = nums.Length; + // 循环,当搜索区间为空时跳出(当 i = j 时为空) + while (i < j) + { + int m = (i + j) / 2; // 计算中点索引 m + if (nums[m] < target) // 此情况说明 target 在区间 [m+1, j) 中 + i = m + 1; + else if (nums[m] > target) // 此情况说明 target 在区间 [i, m) 中 + j = m; + else // 找到目标元素,返回其索引 + return m; + } + // 未找到目标元素,返回 -1 + return -1; + } ``` ### 两种表示对比 @@ -383,7 +419,10 @@ $$ === "C#" ```csharp title="" - + // (i + j) 有可能超出 int 的取值范围 + int m = (i + j) / 2; + // 更换为此写法则不会越界 + int m = i + (j - i) / 2; ``` ## 复杂度分析 diff --git a/docs/chapter_searching/hashing_search.md b/docs/chapter_searching/hashing_search.md index b5537212e..a52f72e21 100644 --- a/docs/chapter_searching/hashing_search.md +++ b/docs/chapter_searching/hashing_search.md @@ -86,7 +86,13 @@ comments: true === "C#" ```csharp title="hashing_search.cs" - + /* 哈希查找(数组) */ + int hashingSearch(Dictionary map, int target) + { + // 哈希表的 key: 目标元素,value: 索引 + // 若哈希表中无此 key ,返回 -1 + return map.GetValueOrDefault(target, -1); + } ``` 再比如,如果我们想要给定一个目标结点值 `target` ,获取对应的链表结点对象,那么也可以使用哈希查找实现。 @@ -163,7 +169,14 @@ comments: true === "C#" ```csharp title="hashing_search.cs" + /* 哈希查找(链表) */ + ListNode? hashingSearch1(Dictionary map, int target) + { + // 哈希表的 key: 目标结点值,value: 结点对象 + // 若哈希表中无此 key ,返回 null + return map.GetValueOrDefault(target); + } ``` ## 复杂度分析 diff --git a/docs/chapter_searching/linear_search.md b/docs/chapter_searching/linear_search.md index e1a4ac142..3284db06c 100644 --- a/docs/chapter_searching/linear_search.md +++ b/docs/chapter_searching/linear_search.md @@ -106,6 +106,19 @@ comments: true === "C#" ```csharp title="linear_search.cs" + /* 线性查找(数组) */ + int linearSearch(int[] nums, int target) + { + // 遍历数组 + for (int i = 0; i < nums.Length; i++) + { + // 找到目标元素,返回其索引 + if (nums[i] == target) + return i; + } + // 未找到目标元素,返回 -1 + return -1; + } ``` @@ -209,7 +222,20 @@ comments: true === "C#" ```csharp title="linear_search.cs" - + /* 线性查找(链表) */ + ListNode? linearSearch(ListNode head, int target) + { + // 遍历链表 + while (head != null) + { + // 找到目标结点,返回之 + if (head.val == target) + return head; + head = head.next; + } + // 未找到目标结点,返回 null + return null; + } ``` ## 复杂度分析 diff --git a/docs/chapter_sorting/bubble_sort.md b/docs/chapter_sorting/bubble_sort.md index 23f7eceed..fa24232a6 100644 --- a/docs/chapter_sorting/bubble_sort.md +++ b/docs/chapter_sorting/bubble_sort.md @@ -176,7 +176,25 @@ comments: true === "C#" ```csharp title="bubble_sort.cs" - + /* 冒泡排序 */ + void bubbleSort(int[] nums) + { + // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + for (int i = nums.Length - 1; i > 0; i--) + { + // 内循环:冒泡操作 + for (int j = 0; j < i; j++) + { + if (nums[j] > nums[j + 1]) + { + // 交换 nums[j] 与 nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + } + } + } + } ``` ## 算法特性 @@ -340,5 +358,26 @@ comments: true === "C#" ```csharp title="bubble_sort.cs" - + /* 冒泡排序(标志优化)*/ + void bubbleSortWithFlag(int[] nums) + { + // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + for (int i = nums.Length - 1; i > 0; i--) + { + bool flag = false; // 初始化标志位 + // 内循环:冒泡操作 + for (int j = 0; j < i; j++) + { + if (nums[j] > nums[j + 1]) + { + // 交换 nums[j] 与 nums[j + 1] + int tmp = nums[j]; + nums[j] = nums[j + 1]; + nums[j + 1] = tmp; + flag = true; // 记录交换元素 + } + } + if (!flag) break; // 此轮冒泡未交换任何元素,直接跳出 + } + } ``` diff --git a/docs/chapter_sorting/insertion_sort.md b/docs/chapter_sorting/insertion_sort.md index b1477213f..3c8b4be7f 100644 --- a/docs/chapter_sorting/insertion_sort.md +++ b/docs/chapter_sorting/insertion_sort.md @@ -141,7 +141,22 @@ comments: true === "C#" ```csharp title="insertion_sort.cs" - + /* 插入排序 */ + void insertionSort(int[] nums) + { + // 外循环:base = nums[1], nums[2], ..., nums[n-1] + for (int i = 1; i < nums.Length; i++) + { + int bas = nums[i], j = i - 1; + // 内循环:将 base 插入到左边的正确位置 + while (j >= 0 && nums[j] > bas) + { + nums[j + 1] = nums[j]; // 1. 将 nums[j] 向右移动一位 + j--; + } + nums[j + 1] = bas; // 2. 将 base 赋值到正确位置 + } + } ``` ## 算法特性 diff --git a/docs/chapter_sorting/merge_sort.md b/docs/chapter_sorting/merge_sort.md index 55f6050cb..95eb2e2cd 100644 --- a/docs/chapter_sorting/merge_sort.md +++ b/docs/chapter_sorting/merge_sort.md @@ -344,7 +344,48 @@ comments: true === "C#" ```csharp title="merge_sort.cs" + /** + * 合并左子数组和右子数组 + * 左子数组区间 [left, mid] + * 右子数组区间 [mid + 1, right] + */ + void merge(int[] nums, int left, int mid, int right) + { + // 初始化辅助数组 + int[] tmp = nums[left..(right + 1)];//Array.CopyOfRange(nums, left, right + 1); + // 左子数组的起始索引和结束索引 + int leftStart = left - left, leftEnd = mid - left; + // 右子数组的起始索引和结束索引 + int rightStart = mid + 1 - left, rightEnd = right - left; + // i, j 分别指向左子数组、右子数组的首元素 + int i = leftStart, j = rightStart; + // 通过覆盖原数组 nums 来合并左子数组和右子数组 + for (int k = left; k <= right; k++) + { + // 若“左子数组已全部合并完”,则选取右子数组元素,并且 j++ + if (i > leftEnd) + nums[k] = tmp[j++]; + // 否则,若“右子数组已全部合并完”或“左子数组元素 < 右子数组元素”,则选取左子数组元素,并且 i++ + else if (j > rightEnd || tmp[i] <= tmp[j]) + nums[k] = tmp[i++]; + // 否则,若“左子数组元素 > 右子数组元素”,则选取右子数组元素,并且 j++ + else + nums[k] = tmp[j++]; + } + } + /* 归并排序 */ + void mergeSort(int[] nums, int left, int right) + { + // 终止条件 + if (left >= right) return; // 当子数组长度为 1 时终止递归 + // 划分阶段 + int mid = (left + right) / 2; // 计算中点 + mergeSort(nums, left, mid); // 递归左子数组 + mergeSort(nums, mid + 1, right); // 递归右子数组 + // 合并阶段 + merge(nums, left, mid, right); + } ``` 下面重点解释一下合并方法 `merge()` 的流程: diff --git a/docs/chapter_sorting/quick_sort.md b/docs/chapter_sorting/quick_sort.md index 8107d1825..4ec7b9ff9 100644 --- a/docs/chapter_sorting/quick_sort.md +++ b/docs/chapter_sorting/quick_sort.md @@ -196,6 +196,30 @@ comments: true === "C#" ```csharp title="quick_sort.cs" + /* 元素交换 */ + void swap(int[] nums, int i, int j) + { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + /* 哨兵划分 */ + int partition(int[] nums, int left, int right) + { + // 以 nums[left] 作为基准数 + int i = left, j = right; + while (i < j) + { + while (i < j && nums[j] >= nums[left]) + j--; // 从右向左找首个小于基准数的元素 + while (i < j && nums[i] <= nums[left]) + i++; // 从左向右找首个大于基准数的元素 + swap(nums, i, j); // 交换这两个元素 + } + swap(nums, i, left); // 将基准数交换至两子数组的分界线 + return i; // 返回基准数的索引 + } ``` @@ -235,7 +259,7 @@ comments: true ```cpp title="quick_sort.cpp" /* 快速排序 */ - static void quickSort(vector& nums, int left, int right) { + void quickSort(vector& nums, int left, int right) { // 子数组长度为 1 时终止递归 if (left >= right) return; @@ -320,6 +344,18 @@ comments: true === "C#" ```csharp title="quick_sort.cs" + /* 快速排序 */ + void quickSort(int[] nums, int left, int right) + { + // 子数组长度为 1 时终止递归 + if (left >= right) + return; + // 哨兵划分 + int pivot = partition(nums, left, right); + // 递归左子数组、右子数组 + quickSort(nums, left, pivot - 1); + quickSort(nums, pivot + 1, right); + } ``` @@ -513,7 +549,39 @@ comments: true === "C#" ```csharp title="quick_sort.cs" + /* 选取三个元素的中位数 */ + int medianThree(int[] nums, int left, int mid, int right) + { + // 使用了异或操作来简化代码 + // 异或规则为 0 ^ 0 = 1 ^ 1 = 0, 0 ^ 1 = 1 ^ 0 = 1 + if ((nums[left] > nums[mid]) ^ (nums[left] > nums[right])) + return left; + else if ((nums[mid] < nums[left]) ^ (nums[mid] < nums[right])) + return mid; + else + return right; + } + /* 哨兵划分(三数取中值) */ + int partition(int[] nums, int left, int right) + { + // 选取三个候选元素的中位数 + int med = medianThree(nums, left, (left + right) / 2, right); + // 将中位数交换至数组最左端 + swap(nums, left, med); + // 以 nums[left] 作为基准数 + int i = left, j = right; + while (i < j) + { + while (i < j && nums[j] >= nums[left]) + j--; // 从右向左找首个小于基准数的元素 + while (i < j && nums[i] <= nums[left]) + i++; // 从左向右找首个大于基准数的元素 + swap(nums, i, j); // 交换这两个元素 + } + swap(nums, i, left); // 将基准数交换至两子数组的分界线 + return i; // 返回基准数的索引 + } ``` ## 尾递归优化 @@ -547,7 +615,7 @@ comments: true ```cpp title="quick_sort.cpp" /* 快速排序(尾递归优化) */ - static void quickSort(vector& nums, int left, int right) { + void quickSort(vector& nums, int left, int right) { // 子数组长度为 1 时终止 while (left < right) { // 哨兵划分操作 @@ -654,5 +722,25 @@ comments: true === "C#" ```csharp title="quick_sort.cs" - + /* 快速排序(尾递归优化) */ + void quickSort(int[] nums, int left, int right) + { + // 子数组长度为 1 时终止 + while (left < right) + { + // 哨兵划分操作 + int pivot = partition(nums, left, right); + // 对两个子数组中较短的那个执行快排 + if (pivot - left < right - pivot) + { + quickSort(nums, left, pivot - 1); // 递归排序左子数组 + left = pivot + 1; // 剩余待排序区间为 [pivot + 1, right] + } + else + { + quickSort(nums, pivot + 1, right); // 递归排序右子数组 + right = pivot - 1; // 剩余待排序区间为 [left, pivot - 1] + } + } + } ``` diff --git a/docs/chapter_stack_and_queue/queue.md b/docs/chapter_stack_and_queue/queue.md index 0bcc90983..f8c864fad 100644 --- a/docs/chapter_stack_and_queue/queue.md +++ b/docs/chapter_stack_and_queue/queue.md @@ -205,7 +205,27 @@ comments: true === "C#" ```csharp title="queue.cs" + /* 初始化队列 */ + Queue queue = new(); + /* 元素入队 */ + queue.Enqueue(1); + queue.Enqueue(3); + queue.Enqueue(2); + queue.Enqueue(5); + queue.Enqueue(4); + + /* 访问队首元素 */ + int peek = queue.Peek(); + + /* 元素出队 */ + int poll = queue.Dequeue(); + + /* 获取队列的长度 */ + int size = queue.Count(); + + /* 判断队列是否为空 */ + bool isEmpty = queue.Count() == 0; ``` ## 队列实现 @@ -532,7 +552,68 @@ comments: true === "C#" ```csharp title="linkedlist_queue.cs" + /* 基于链表实现的队列 */ + class LinkedListQueue + { + private ListNode? front, rear; // 头结点 front ,尾结点 rear + private int queSize = 0; + public LinkedListQueue() + { + front = null; + rear = null; + } + + /* 获取队列的长度 */ + public int size() + { + return queSize; + } + + /* 判断队列是否为空 */ + public bool isEmpty() + { + return size() == 0; + } + + /* 入队 */ + public void offer(int num) + { + // 尾结点后添加 num + ListNode node = new ListNode(num); + // 如果队列为空,则令头、尾结点都指向该结点 + if (front == null) + { + front = node; + rear = node; + // 如果队列不为空,则将该结点添加到尾结点后 + } + else if (rear != null) + { + rear.next = node; + rear = node; + } + queSize++; + } + + /* 出队 */ + public int poll() + { + int num = peek(); + // 删除头结点 + front = front?.next; + queSize--; + return num; + } + + /* 访问队首元素 */ + public int peek() + { + if (size() == 0 || front == null) + throw new Exception(); + return front.val; + } + } ``` ### 基于数组的实现 @@ -880,7 +961,61 @@ comments: true === "C#" ```csharp title="array_queue.cs" + /* 基于环形数组实现的队列 */ + class ArrayQueue { + private int[] nums; // 用于存储队列元素的数组 + private int front = 0; // 头指针,指向队首 + private int rear = 0; // 尾指针,指向队尾 + 1 + public ArrayQueue(int capacity) { + // 初始化数组 + nums = new int[capacity]; + } + + /* 获取队列的容量 */ + public int capacity() { + return nums.Length; + } + + /* 获取队列的长度 */ + public int size() { + int capacity = this.capacity(); + // 由于将数组看作为环形,可能 rear < front ,因此需要取余数 + return (capacity + rear - front) % capacity; + } + + /* 判断队列是否为空 */ + public bool isEmpty() { + return rear - front == 0; + } + + /* 入队 */ + public void offer(int num) { + if (size() == capacity()) { + Console.WriteLine("队列已满"); + return; + } + // 尾结点后添加 num + nums[rear] = num; + // 尾指针向后移动一位,越过尾部后返回到数组头部 + rear = (rear + 1) % capacity(); + } + + /* 出队 */ + public int poll() { + int num = peek(); + // 队头指针向后移动一位,若越过尾部则返回到数组头部 + front = (front + 1) % capacity(); + return num; + } + + /* 访问队首元素 */ + public int peek() { + if (isEmpty()) + throw new Exception(); + return nums[front]; + } + } ``` ## 队列典型应用 diff --git a/docs/chapter_stack_and_queue/stack.md b/docs/chapter_stack_and_queue/stack.md index e53e42218..d3de9d06e 100644 --- a/docs/chapter_stack_and_queue/stack.md +++ b/docs/chapter_stack_and_queue/stack.md @@ -203,7 +203,27 @@ comments: true === "C#" ```csharp title="stack.cs" + /* 初始化栈 */ + Stack stack = new (); + /* 元素入栈 */ + stack.Push(1); + stack.Push(3); + stack.Push(2); + stack.Push(5); + stack.Push(4); + + /* 访问栈顶元素 */ + int peek = stack.Peek(); + + /* 元素出栈 */ + int pop = stack.Pop(); + + /* 获取栈的长度 */ + int size = stack.Count(); + + /* 判断是否为空 */ + bool isEmpty = stack.Count()==0; ``` ## 栈的实现 @@ -408,7 +428,54 @@ comments: true === "C#" ```csharp title="linkedlist_stack.cs" + /* 基于链表实现的栈 */ + class LinkedListStack + { + private ListNode stackPeek; // 将头结点作为栈顶 + private int stkSize = 0; // 栈的长度 + public LinkedListStack() + { + stackPeek = null; + } + /* 获取栈的长度 */ + public int size() + { + return stkSize; + } + + /* 判断栈是否为空 */ + public bool isEmpty() + { + return size() == 0; + } + + /* 入栈 */ + public void push(int num) + { + ListNode node = new ListNode(num); + node.next = stackPeek; + stackPeek = node; + stkSize++; + } + + /* 出栈 */ + public int pop() + { + int num = peek(); + stackPeek = stackPeek?.next; + stkSize--; + return num; + } + + /* 访问栈顶元素 */ + public int peek() + { + if (size() == 0) + throw new Exception(); + return stackPeek.val; + } + } ``` ### 基于数组的实现 @@ -648,7 +715,52 @@ comments: true === "C#" ```csharp title="array_stack.cs" + /* 基于数组实现的栈 */ + class ArrayStack + { + private List stack; + public ArrayStack() + { + // 初始化列表(动态数组) + stack = new(); + } + /* 获取栈的长度 */ + public int size() + { + return stack.Count(); + } + + /* 判断栈是否为空 */ + public bool isEmpty() + { + return size() == 0; + } + + /* 入栈 */ + public void push(int num) + { + stack.Add(num); + } + + /* 出栈 */ + public int pop() + { + if (isEmpty()) + throw new Exception(); + var val = peek(); + stack.RemoveAt(size() - 1); + return val; + } + + /* 访问栈顶元素 */ + public int peek() + { + if (isEmpty()) + throw new Exception(); + return stack[size() - 1]; + } + } ``` !!! tip diff --git a/docs/chapter_tree/avl_tree.md b/docs/chapter_tree/avl_tree.md index 35397f835..42131202c 100644 --- a/docs/chapter_tree/avl_tree.md +++ b/docs/chapter_tree/avl_tree.md @@ -78,7 +78,14 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "C#" ```csharp title="avl_tree.cs" - + /* AVL 树结点类 */ + class TreeNode { + public int val; // 结点值 + public int height; // 结点高度 + public TreeNode left; // 左子结点 + public TreeNode right; // 右子结点 + public TreeNode(int x) { val = x; } + } ``` 「结点高度」是最远叶结点到该结点的距离,即走过的「边」的数量。需要特别注意,**叶结点的高度为 0 ,空结点的高度为 -1** 。我们封装两个工具函数,分别用于获取与更新结点的高度。 @@ -138,7 +145,19 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "C#" ```csharp title="avl_tree.cs" - + /* 获取结点高度 */ + public int height(TreeNode? node) + { + // 空结点高度为 -1 ,叶结点高度为 0 + return node == null ? -1 : node.height; + } + + /* 更新结点高度 */ + private void updateHeight(TreeNode node) + { + // 结点高度等于最高子树高度 + 1 + node.height = Math.Max(height(node.left), height(node.right)) + 1; + } ``` ### 结点平衡因子 @@ -196,7 +215,14 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit === "C#" ```csharp title="avl_tree.cs" - + /* 获取平衡因子 */ + public int balanceFactor(TreeNode? node) + { + // 空结点平衡因子为 0 + if (node == null) return 0; + // 结点平衡因子 = 左子树高度 - 右子树高度 + return height(node.left) - height(node.right); + } ``` !!! note @@ -285,6 +311,23 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 === "C#" ```csharp title="avl_tree.cs" + /* 右旋操作 */ + TreeNode? rightRotate(TreeNode? node) + { + if (node == null) + return null; + + TreeNode? child = node.left; + TreeNode? grandChild = child?.right; + // 以 child 为原点,将 node 向右旋转 + child.right = node; + node.left = grandChild; + // 更新结点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } ``` @@ -353,7 +396,23 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 === "C#" ```csharp title="avl_tree.cs" - + /* 左旋操作 */ + TreeNode? leftRotate(TreeNode? node) + { + if (node == null) + return null; + + TreeNode? child = node.right; + TreeNode? grandChild = child?.left; + // 以 child 为原点,将 node 向左旋转 + child.left = node; + node.right = grandChild; + // 更新结点高度 + updateHeight(node); + updateHeight(child); + // 返回旋转后子树的根节点 + return child; + } ``` ### Case 3 - 先左后右 @@ -462,7 +521,47 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 === "C#" ```csharp title="avl_tree.cs" - + /* 执行旋转操作,使该子树重新恢复平衡 */ + TreeNode? rotate(TreeNode? node) + { + if (node == null) + return node; + + // 获取结点 node 的平衡因子 + int balanceFactorInt = balanceFactor(node); + // 左偏树 + if (balanceFactorInt > 1) + { + if (balanceFactor(node.left) >= 0) + { + // 右旋 + return rightRotate(node); + } + else + { + // 先左旋后右旋 + node.left = leftRotate(node?.left); + return rightRotate(node); + } + } + // 右偏树 + if (balanceFactorInt < -1) + { + if (balanceFactor(node.right) <= 0) + { + // 左旋 + return leftRotate(node); + } + else + { + // 先右旋后左旋 + node.right = rightRotate(node?.right); + return leftRotate(node); + } + } + // 平衡树,无需旋转,直接返回 + return node; + } ``` ## AVL 树常用操作 @@ -537,6 +636,30 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 === "C#" ```csharp title="avl_tree.cs" + /* 插入结点 */ + public TreeNode? insert(int val) + { + root = insertHelper(root, val); + return root; + } + + /* 递归插入结点(辅助函数) */ + private TreeNode? insertHelper(TreeNode? node, int val) + { + if (node == null) return new TreeNode(val); + /* 1. 查找插入位置,并插入结点 */ + if (val < node.val) + node.left = insertHelper(node.left, val); + else if (val > node.val) + node.right = insertHelper(node.right, val); + else + return node; // 重复结点不插入,直接返回 + updateHeight(node); // 更新结点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } ``` @@ -634,7 +757,60 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影 === "C#" ```csharp title="avl_tree.cs" - + /* 删除结点 */ + public TreeNode? remove(int val) + { + root = removeHelper(root, val); + return root; + } + + /* 递归删除结点(辅助函数) */ + private TreeNode? removeHelper(TreeNode? node, int? val) + { + if (node == null) return null; + /* 1. 查找结点,并删除之 */ + if (val < node.val) + node.left = removeHelper(node.left, val); + else if (val > node.val) + node.right = removeHelper(node.right, val); + else + { + if (node.left == null || node.right == null) + { + TreeNode? child = node.left != null ? node.left : node.right; + // 子结点数量 = 0 ,直接删除 node 并返回 + if (child == null) + return null; + // 子结点数量 = 1 ,直接删除 node + else + node = child; + } + else + { + // 子结点数量 = 2 ,则将中序遍历的下个结点删除,并用该结点替换当前结点 + TreeNode? temp = minNode(node.right); + node.right = removeHelper(node.right, temp?.val); + node.val = temp?.val; + } + } + updateHeight(node); // 更新结点高度 + /* 2. 执行旋转操作,使该子树重新恢复平衡 */ + node = rotate(node); + // 返回子树的根节点 + return node; + } + + /* 获取最小结点 */ + private TreeNode? minNode(TreeNode? node) + { + if (node == null) return node; + // 循环访问左子结点,直到叶结点时为最小结点,跳出 + while (node.left != null) + { + node = node.left; + } + return node; + } ``` ### 查找结点 diff --git a/docs/chapter_tree/binary_search_tree.md b/docs/chapter_tree/binary_search_tree.md index ff04e0622..a01b41807 100644 --- a/docs/chapter_tree/binary_search_tree.md +++ b/docs/chapter_tree/binary_search_tree.md @@ -159,7 +159,23 @@ comments: true === "C#" ```csharp title="binary_search_tree.cs" - + /* 查找结点 */ + TreeNode? search(int num) + { + TreeNode? cur = root; + // 循环查找,越过叶结点后跳出 + while (cur != null) + { + // 目标结点在 root 的右子树中 + if (cur.val < num) cur = cur.right; + // 目标结点在 root 的左子树中 + else if (cur.val > num) cur = cur.left; + // 找到目标结点,跳出循环 + else break; + } + // 返回目标结点 + return cur; + } ``` ### 插入结点 @@ -335,7 +351,33 @@ comments: true === "C#" ```csharp title="binary_search_tree.cs" + /* 插入结点 */ + TreeNode? insert(int num) + { + // 若树为空,直接提前返回 + if (root == null) return null; + TreeNode? cur = root, pre = null; + // 循环查找,越过叶结点后跳出 + while (cur != null) + { + // 找到重复结点,直接返回 + if (cur.val == num) return null; + pre = cur; + // 插入位置在 root 的右子树中 + if (cur.val < num) cur = cur.right; + // 插入位置在 root 的左子树中 + else cur = cur.left; + } + // 插入结点 val + TreeNode node = new TreeNode(num); + if (pre != null) + { + if (pre.val < num) pre.right = node; + else pre.left = node; + } + return node; + } ``` 为了插入结点,需要借助 **辅助结点 `prev`** 保存上一轮循环的结点,这样在遍历到 $\text{null}$ 时,我们也可以获取到其父结点,从而完成结点插入操作。 @@ -649,7 +691,69 @@ comments: true === "C#" ```csharp title="binary_search_tree.cs" + /* 删除结点 */ + TreeNode? remove(int? num) + { + // 若树为空,直接提前返回 + if (root == null) return null; + TreeNode? cur = root, pre = null; + // 循环查找,越过叶结点后跳出 + while (cur != null) + { + // 找到待删除结点,跳出循环 + if (cur.val == num) break; + pre = cur; + // 待删除结点在 root 的右子树中 + if (cur.val < num) cur = cur.right; + // 待删除结点在 root 的左子树中 + else cur = cur.left; + } + // 若无待删除结点,则直接返回 + if (cur == null || pre == null) return null; + // 子结点数量 = 0 or 1 + if (cur.left == null || cur.right == null) + { + // 当子结点数量 = 0 / 1 时, child = null / 该子结点 + TreeNode? child = cur.left != null ? cur.left : cur.right; + // 删除结点 cur + if (pre.left == cur) + { + pre.left = child; + } + else + { + pre.right = child; + } + } + // 子结点数量 = 2 + else + { + // 获取中序遍历中 cur 的下一个结点 + TreeNode? nex = min(cur.right); + if (nex != null) + { + int? tmp = nex.val; + // 递归删除结点 nex + remove(nex.val); + // 将 nex 的值复制给 cur + cur.val = tmp; + } + } + return cur; + } + + /* 获取最小结点 */ + TreeNode? min(TreeNode? root) + { + if (root == null) return root; + // 循环访问左子结点,直到叶结点时为最小结点,跳出 + while (root.left != null) + { + root = root.left; + } + return root; + } ``` ## 二叉搜索树的优势 diff --git a/docs/chapter_tree/binary_tree.md b/docs/chapter_tree/binary_tree.md index ed4582904..e88077276 100644 --- a/docs/chapter_tree/binary_tree.md +++ b/docs/chapter_tree/binary_tree.md @@ -97,7 +97,13 @@ comments: true === "C#" ```csharp title="" - + /* 链表结点类 */ + class TreeNode { + int val; // 结点值 + TreeNode left; // 左子结点指针 + TreeNode right; // 右子结点指针 + TreeNode(int x) { val = x; } + } ``` 结点的两个指针分别指向「左子结点 Left Child Node」和「右子结点 Right Child Node」,并且称该结点为两个子结点的「父结点 Parent Node」。给定二叉树某结点,将左子结点以下的树称为该结点的「左子树 Left Subtree」,右子树同理。 @@ -232,7 +238,18 @@ comments: true === "C#" ```csharp title="binary_tree.cs" - + /* 初始化二叉树 */ + // 初始化结点 + TreeNode n1 = new TreeNode(1); + TreeNode n2 = new TreeNode(2); + TreeNode n3 = new TreeNode(3); + TreeNode n4 = new TreeNode(4); + TreeNode n5 = new TreeNode(5); + // 构建引用指向(即指针) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; ``` **插入与删除结点。** 与链表类似,插入与删除结点都可以通过修改指针实现。 @@ -315,7 +332,13 @@ comments: true === "C#" ```csharp title="binary_tree.cs" - + /* 插入与删除结点 */ + TreeNode P = new TreeNode(0); + // 在 n1 -> n2 中间插入结点 P + n1.left = P; + P.left = n2; + // 删除结点 P + n1.left = n2; ``` !!! note @@ -446,7 +469,9 @@ comments: true === "C#" ```csharp title="" - + /* 二叉树的数组表示 */ + // 使用 int?可空类型 ,就可以使用 null 来标记空位 + int?[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 }; ``` ![array_representation_with_empty](binary_tree.assets/array_representation_with_empty.png) diff --git a/docs/chapter_tree/binary_tree_traversal.md b/docs/chapter_tree/binary_tree_traversal.md index 129d297c6..2facda257 100644 --- a/docs/chapter_tree/binary_tree_traversal.md +++ b/docs/chapter_tree/binary_tree_traversal.md @@ -149,6 +149,25 @@ comments: true === "C#" ```csharp title="binary_tree_bfs.cs" + /* 层序遍历 */ + public List hierOrder(TreeNode root) + { + // 初始化队列,加入根结点 + Queue queue = new(); + queue.Enqueue(root); + // 初始化一个列表,用于保存遍历序列 + List list = new(); + while (queue.Count != 0) + { + TreeNode node = queue.Dequeue(); // 队列出队 + list.Add(node.val); // 保存结点值 + if (node.left != null) + queue.Enqueue(node.left); // 左子结点入队 + if (node.right != null) + queue.Enqueue(node.right); // 右子结点入队 + } + return list; + } ``` @@ -354,6 +373,35 @@ comments: true === "C#" ```csharp title="binary_tree_dfs.cs" + /* 前序遍历 */ + void preOrder(TreeNode? root) + { + if (root == null) return; + // 访问优先级:根结点 -> 左子树 -> 右子树 + list.Add(root.val); + preOrder(root.left); + preOrder(root.right); + } + + /* 中序遍历 */ + void inOrder(TreeNode? root) + { + if (root == null) return; + // 访问优先级:左子树 -> 根结点 -> 右子树 + inOrder(root.left); + list.Add(root.val); + inOrder(root.right); + } + + /* 后序遍历 */ + void postOrder(TreeNode? root) + { + if (root == null) return; + // 访问优先级:左子树 -> 右子树 -> 根结点 + postOrder(root.left); + postOrder(root.right); + list.Add(root.val); + } ```