diff --git a/csharp/06_linkedlist/BaseLinkedListTests.cs b/csharp/06_linkedlist/BaseLinkedListTests.cs new file mode 100644 index 00000000..b09568f1 --- /dev/null +++ b/csharp/06_linkedlist/BaseLinkedListTests.cs @@ -0,0 +1,19 @@ +using System; + +namespace _06_linked_list +{ + public class BaseLinkedListTests + { + protected void PrintList (SingleLinkedList list) where T : IComparable + { + if (list == null) return; + + var p = list.First; + while (p != null) + { + System.Console.WriteLine (p.Value); + p = p.Next; + } + } + } +} \ No newline at end of file diff --git a/csharp/06_linkedlist/LRULinkedList.Tests.cs b/csharp/06_linkedlist/LRULinkedList.Tests.cs new file mode 100644 index 00000000..7df7dbb6 --- /dev/null +++ b/csharp/06_linkedlist/LRULinkedList.Tests.cs @@ -0,0 +1,63 @@ +using Xunit; +using Xunit.Abstractions; + +namespace _06_linked_list +{ + public class LRULinkedListTests : BaseLinkedListTests + { + [Fact] + public void LRU_Set_Value_When_Not_Existed() + { + var lru = new LRULinkedList(); + + lru.Set(1); + lru.Set(3); + lru.Set(5); + lru.Set(7); + lru.Set(9); + + var list = lru.GetCachedList(); + + PrintList(list); + + Assert.Equal(9, list.First.Value); + } + + [Fact] + public void LRU_Set_Value_When_Existed() + { + var lru = new LRULinkedList(); + + lru.Set(1); + lru.Set(3); + lru.Set(5); + lru.Set(7); + lru.Set(3); + + var list = lru.GetCachedList(); + + PrintList(list); + + Assert.Equal(3, list.First.Value); + } + + [Fact] + public void LRU_Set_Value_When_Full() + { + var lru = new LRULinkedList(5); + + lru.Set(1); + lru.Set(3); + lru.Set(5); + lru.Set(7); + lru.Set(9); + lru.Set(8); + + var list = lru.GetCachedList(); + + PrintList(list); + + Assert.Equal(8, list.First.Value); + } + } +} \ No newline at end of file diff --git a/csharp/06_linkedlist/LRULinkedList.cs b/csharp/06_linkedlist/LRULinkedList.cs new file mode 100644 index 00000000..3bbff324 --- /dev/null +++ b/csharp/06_linkedlist/LRULinkedList.cs @@ -0,0 +1,63 @@ +namespace _06_linked_list +{ + /// + /// 使用单链表实现LRU缓存淘汰算法 + /// + /// + /// 思路: + /// 维护一个有序单链表,越是靠近链尾的数据是最早访问的。当有一个新的数据被访问时, + /// 1. 如果数据在缓存中,则将其从原位置删除,然后插入到表头; + /// 2. 如果数据不在缓存中,有两种情况: + /// 1) 链表未满,则将数据插入到表头; + /// 2) 链表已满,则删除尾结点,将新数据插入到表头。 + /// + public class LRULinkedList + { + private readonly SingleLinkedList _cachedList = new SingleLinkedList(); + private readonly int _capacity; + + /// + /// 构造函数 + /// + /// 缓存容量 + public LRULinkedList(int capacity = 10) + { + _capacity = capacity; + } + + /// + /// 存储缓存数据 + /// + /// + public void Set(int val) + { + var deletedNode = _cachedList.Delete(value: val); + + // 数据在缓存中存在,从原位置删除,然后插入到表头 + if (deletedNode != null) + { + _cachedList.Insert(1, val); + return; + } + + // 数据不存在 + if (_cachedList.Length != _capacity) + { + // 链表未满 + _cachedList.Insert(1, val); + } + else + { + // 链表已满,删除尾结点,将新数据插入到头部 + _cachedList.Delete(_cachedList.Length); + _cachedList.Insert(1, val); + } + } + + public SingleLinkedList GetCachedList() + { + return _cachedList; + } + } + +} \ No newline at end of file diff --git a/csharp/06_linkedlist/SingleLinkedList.Tests.cs b/csharp/06_linkedlist/SingleLinkedList.Tests.cs new file mode 100644 index 00000000..e7c670af --- /dev/null +++ b/csharp/06_linkedlist/SingleLinkedList.Tests.cs @@ -0,0 +1,309 @@ +using System; +using Xunit; +using Xunit.Abstractions; + +namespace _06_linked_list +{ + public class SingleLinkedListTests : BaseLinkedListTests + { + [Fact] + public void Insert_3_Elements_Return_Length_3() + { + var list = new SingleLinkedList("The", "Quick", "Brown"); + + PrintList(list); + + Assert.Equal(3, list.Length); + } + + [Fact] + public void Insert_Some_Elements_Then_Verify_First() + { + var list = new SingleLinkedList("The", "Quick", "Brown"); + + Assert.Equal("The", list.First.Value); + } + + [Fact] + public void Insert_Some_Elements_Then_Verify_Last() + { + var list = new SingleLinkedList("The", "Quick", "Brown"); + + Assert.Equal("Brown", list.First.Next.Next.Value); + } + + [Fact] + public void Find_Return_Null_When_Postion_LessThan_1() + { + var list = new SingleLinkedList("The", "Quick", "Brown"); + + var node = list.Find(0); + Assert.Null(node); + } + + [Fact] + public void Find_Return_Null_When_Postion_GreaterThan_Length() + { + var list = new SingleLinkedList("The", "Quick", "Brown"); + + var node = list.Find(4); + Assert.Null(node); + } + + [Fact] + public void Find_Return_Correct_When_Postion_Valid() + { + var list = new SingleLinkedList("The", "Quick", "Brown"); + + var node = list.Find(2); + Assert.Equal("Quick", node.Value); + } + + [Fact] + public void Delete_Return_Null_When_Postion_LessThan_1() + { + var list = new SingleLinkedList("The", "Quick", "Brown"); + + var node = list.Delete(0); + Assert.Null(node); + } + + [Fact] + public void Delete_Return_Null_When_Postion_GreaterThan_Length() + { + var list = new SingleLinkedList("The", "Quick", "Brown"); + + var node = list.Delete(4); + Assert.Null(node); + } + + [Fact] + public void Delete_By_Value_Success_When_Element_Exist() + { + var list = new SingleLinkedList("The", "Quick", "Brown", "Fox", "jumps", "over", "the", "lazy", + "dog"); + + var deletedNode = list.Delete("over"); + + PrintList(list); + + Assert.Equal("over", deletedNode.Value); + Assert.Equal(8, list.Length); + } + + [Fact] + public void Delete_By_Value_Success_When_Element_Not_Exist() + { + var list = new SingleLinkedList("The", "Quick", "Brown", "Fox", "jumps", "over", "the", "lazy", + "dog"); + + var deletedNode = list.Delete("hello"); + + PrintList(list); + + Assert.Null(deletedNode); + Assert.Equal(9, list.Length); + } + + [Fact] + public void Delete_By_Value_Success_When_Delete_First() + { + var list = new SingleLinkedList("The", "Quick", "Brown", "Fox", "jumps", "over", "the", "lazy", + "dog"); + + var deletedNode = list.Delete("The"); + + PrintList(list); + + Assert.Equal("The", deletedNode.Value); + Assert.Equal(8, list.Length); + } + + [Fact] + public void Delete_By_Value_Success_When_Delete_Last() + { + var list = new SingleLinkedList("The", "Quick", "Brown", "Fox", "jumps", "over", "the", "lazy", + "dog"); + + var deletedNode = list.Delete("dog"); + + PrintList(list); + + Assert.Equal("dog", deletedNode.Value); + Assert.Equal(8, list.Length); + } + + [Fact] + public void Delete_Success_When_Position_Valid() + { + var list = new SingleLinkedList("The", "Quick", "Brown", "Fox", "jumps", "over", "the", "lazy", + "dog"); + + var node = list.Delete(3); + + PrintList(list); + + Assert.Equal("Brown", node.Value); + Assert.Equal(8, list.Length); + } + + [Fact] + public void Clear_Length_Equal_0() + { + var list = new SingleLinkedList("The", "Quick", "Brown"); + + list.Clear(); + + Assert.Equal(0, list.Length); + } + + [Fact] + public void Clear_First_Is_Null() + { + var list = new SingleLinkedList("The", "Quick", "Brown"); + + list.Clear(); + + Assert.Null(list.First); + } + + [Fact] + public void Reverse_When_List_Is_Empty() + { + var list = new SingleLinkedList(); + + list.Reverse(); + + PrintList(list); + + Assert.Null(list.First); + } + + [Fact] + public void Reverse_When_List_Has_Many_Elements() + { + var list = new SingleLinkedList("The", "Quick", "Brown", "Fox", "jumps", "over", "the", "lazy", + "dog"); + + list.Reverse(); + + PrintList(list); + + Assert.True(list.First.Value == "dog"); + } + + [Fact] + public void HasCycle_List_Empty() + { + var list = new SingleLinkedList("The", "Quick", "Brown", "Fox", "jumps", "over", "the", "lazy", + "dog"); + + bool hasCycle = list.HasCycle(); + + Assert.False(hasCycle); + } + + [Fact] + public void HasCycle_False_When_List_Length_1() + { + var list = new SingleLinkedList("The"); + + bool hasCycle = list.HasCycle(); + + Assert.False(hasCycle); + } + + [Fact] + public void HasCycle_False_When_List_Length_2() + { + var list = new SingleLinkedList("The", "Quick"); + + bool hasCycle = list.HasCycle(); + + Assert.False(hasCycle); + } + + [Fact] + public void HasCycle_True_When_List_Length_2() + { + var list = new SingleLinkedList(); + + var firstNode = list.Insert(1, "The"); + var secondNode = list.Insert(2, "Quick"); + + secondNode.Next = firstNode; + + bool hasCycle = list.HasCycle(); + + Assert.True(hasCycle); + } + + [Fact] + public void HasCycle_False() + { + var linkList = + new SingleLinkedList("The", "Quick", "Brown", "fox", "jumps", "over", "the", "lazy", "dog"); + + bool hasCycle = linkList.HasCycle(); + + Assert.False(hasCycle); + } + + [Fact] + public void HasCycle_True() + { + var list = new SingleLinkedList(); + + // 初始化一个具有环的链表 + list.Insert(1, "The"); + list.Insert(2, "Quick"); + list.Insert(3, "Brown"); + var fourthNode = list.Insert(4, "fox"); + list.Insert(5, "jumps"); + list.Insert(6, "over"); + list.Insert(7, "the"); + list.Insert(8, "lazy"); + var last = list.Insert(9, "dog"); + + last.Next = fourthNode; + + bool hasCycle = list.HasCycle(); + + Assert.True(hasCycle); + } + + [Fact] + public void Merge() + { + var list1 = new SingleLinkedList(1, 2, 4); + var list2 = new SingleLinkedList(1, 3, 4); + + var list3 = list1.Merge(list2); + + PrintList(list3); + + Assert.True(list1.First.Next.Next.Value == 2); + } + + [Fact] + public void Remove_2th_Node_From_End() + { + var list = new SingleLinkedList(1, 2, 3, 4, 5); + list.RemoveNthNodeFromEnd(2); + + PrintList(list); + + Assert.True(list.First.Next.Next.Next.Value == 5); + } + + [Fact] + public void FindMiddleNode() + { + var list = new SingleLinkedList(1, 2, 3, 4, 5); + + LinkedListNode middleNode = list.FindMiddleNode(); + + Assert.True(middleNode.Value == 3); + } + } +} \ No newline at end of file diff --git a/csharp/06_linkedlist/SingleLinkedList.cs b/csharp/06_linkedlist/SingleLinkedList.cs new file mode 100644 index 00000000..59ad6ba5 --- /dev/null +++ b/csharp/06_linkedlist/SingleLinkedList.cs @@ -0,0 +1,320 @@ +using System; + +namespace _06_linked_list +{ + /// + /// 单链表的插入、删除、清空、查找 + /// 1. 链表反转 + /// 2. 环的检测 + /// 3. 两个有序链表的合并 + /// 4. 删除链表倒数第n个结点 + /// 5. 求链表的中间结点 + /// + /// + public class SingleLinkedList where T : IComparable + { + public SingleLinkedList () + { + Head = new LinkedListNode (default (T)); + } + + public SingleLinkedList (params T[] list) + { + Head = new LinkedListNode (default (T)); + if (list == null) return; + + var p = Head; + foreach (var item in list) + { + var q = new LinkedListNode (item); + p.Next = q; + p = q; + } + + Length = list.Length; + } + + // Head node + public LinkedListNode First => Head.Next; + public LinkedListNode Head { get; } + + public int Length { get; private set; } + + public LinkedListNode Insert (int position, T newElem) + { + if (position < 1 || position > Length + 1) + { + throw new IndexOutOfRangeException ("Position must be in bound of list"); + } + + var p = Head; + + int j = 1; + while (p != null && j < position) + { + p = p.Next; + ++j; + } + + var newNode = new LinkedListNode (newElem); + newNode.Next = p.Next; + p.Next = newNode; + + Length++; + + return newNode; + } + + public LinkedListNode Find (int position) + { + LinkedListNode p = First; + int j = 1; + + while (p != null && j < position) + { + p = p.Next; + j++; + } + + if (p == null || j > position) + { + return null; + } + + return p; + } + + public LinkedListNode Find (T elem) + { + LinkedListNode p = Head.Next; + + while (p != null) + { + if (p.Value.CompareTo (elem) == 0) return p; + + p = p.Next; + } + + return null; + } + + public LinkedListNode Delete (T value) + { + LinkedListNode cur = Head; + while (cur.Next != null && cur.Next.Value.CompareTo (value) != 0) + { + cur = cur.Next; + } + + if (cur.Next == null) return null; + + var q = cur.Next; + cur.Next = q.Next; + + Length--; + + return q; + } + + public LinkedListNode Delete (int position) + { + if (position < 1 || position > Length) + { + return null; + } + + var p = First; + int j = 1; + while (p != null && j < position - 1) + { + p = p.Next; + ++j; + } + + var q = p.Next; + p.Next = q.Next; + + Length--; + + return q; + } + + public void Clear () + { + var cur = Head; + while (cur.Next != null) + { + var q = cur.Next; + cur.Next = null; + + cur = q; + } + + Length = 0; + } + + /// + /// reverse current list + /// + public void Reverse () + { + if (Length <= 1) return; + + LinkedListNode p = First; + LinkedListNode q = First.Next; + + LinkedListNode r = null; + + p.Next = null; + while (q != null) + { + r = q.Next; + + q.Next = p; + p = q; + q = r; + } + + Head.Next = p; + } + + /// + /// 环的检测 + /// + /// + /// 用快慢两个指针,快指针每次移动2个结点,慢指针每次移动1个结点,当两个指针相遇时,说明存在环。 + /// LeetCode 编号: 141 + /// + public bool HasCycle () + { + if (Length == 0) return false; + + var slow = Head.Next; + var fast = Head.Next.Next; + + while (fast != null && slow != null && fast != slow) + { + fast = fast.Next?.Next; + slow = slow.Next; + } + + bool ret = fast == slow; + return ret; + } + + /// + /// 合并两个有序链表(从小到大) + /// + /// LeetCode 编号: 21 + /// + /// + public SingleLinkedList Merge (SingleLinkedList list) + { + if (list == null) return null; + + var root = new SingleLinkedList (); + + LinkedListNode pointer = root.Head; // 总是向新链表的尾结点 + + var head1 = list.First; + var head2 = this.First; + + while (head1 != null && head2 != null) + { + if (head1.Value.CompareTo (head2.Value) < 0) + { + pointer.Next = head1; + head1 = head1.Next; + } + else + { + pointer.Next = head2; + head2 = head2.Next; + } + + pointer = pointer.Next; // 指向尾结点 + } + + if (head1 != null) + { + pointer.Next = head1; + } + + if (head2 != null) + { + pointer.Next = head2; + } + + return root; + } + + /// + /// 删除倒数第n个结点 + /// + /// + /// 用快慢两个指针,快指针比慢指针早n个结点,然后再同步移动两个指针,当快指针指向尾结点时,慢指针就是将要删除的结点 + /// LeetCode 编号: 19 + /// + /// + public void RemoveNthNodeFromEnd (int n) + { + if (n < 1 || n > Length) return; + + LinkedListNode preNode = Head; + LinkedListNode curNode = Head; + + for (int i = 0; i < n; i++) + { + curNode = curNode.Next; + } + + if (curNode == null) return; + + while (curNode.Next != null) + { + preNode = preNode.Next; + curNode = curNode.Next; + } + + preNode.Next = preNode.Next.Next; + } + + /// + /// 链表的中间结点 + /// + /// + /// 思路: 利用快慢指针,快指针步长2,慢指针步长1,当快指针到达尾结点时,慢指针正好到达中间结点 + /// LeetCode 编号: 876 + /// + /// + public LinkedListNode FindMiddleNode () + { + if (First?.Next == null) return null; + + LinkedListNode slowPointer = First; + LinkedListNode fastPointer = First.Next; + + while (fastPointer.Next?.Next != null) + { + fastPointer = fastPointer.Next.Next; + slowPointer = slowPointer.Next; + } + + slowPointer = slowPointer.Next; + return slowPointer; + } + } + + public class LinkedListNode + { + private T _value; + + public LinkedListNode (T value) + { + _value = value; + } + + public T Value => _value; + public LinkedListNode Next { get; set; } + } +} \ No newline at end of file diff --git a/csharp/06_linkedlist/_06_linked_list.csproj b/csharp/06_linkedlist/_06_linked_list.csproj new file mode 100644 index 00000000..3e025101 --- /dev/null +++ b/csharp/06_linkedlist/_06_linked_list.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp2.2 + + false + + + + + + + + +