資料結構,你還記得嗎(下)
跟上一篇《資料結構,你還記得嗎(中)》目錄進行一一對應,以此來提升理解。
陣列
陣列反轉
int[] arr = { 1, 2, 3, 5, 6 }; int length = arr.Length / 2; for(int i=0;i<length; i++) { int temp = arr[i]; arr[i] = arr[arr.Length - i-1]; arr[arr.Length - i-1] = temp; }
List和ArrayList自帶反轉函式 Reverse();
尋找陣列中第二小的元素
- 解決方案有按遞增順序對陣列進行排序,堆排、快排、歸併排序等等都可以達到目的。時間複雜度是O(nlogn)。
- 其次是掃描陣列兩次。在第一次遍歷中找到最小元素。在第二次遍歷中,找到大於它的最小元素,時間複雜度是O(n)。
下面介紹一次遍歷解決,時間複雜度是O(n)。
Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); int min =int.MaxValue; int secondMin = int.MaxValue; int[] arr = { 1, 3, 5, 6, 8, 9, 10, 66 , 66 ,55,88,66,22,55,58}; for (int i = 0; i < arr.Length; i++) { int num = arr[i]; if (num < min) { secondMin = min; min = num; } else secondMin = num < secondMin ? num : secondMin; }; stopwatch.Stop(); Console.WriteLine(secondMin+"花費時間{0}",stopwatch.Elapsed.ToString());
找出陣列中第一個不重複的元素
- 笨方法
Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); int[] arr = { 1, 3, 5, 6, 8, 9, 10, 66 , 66 ,55,88,66,22,55,581,1,3,5,6}; Dictionary<int, List<int>> dic = new Dictionary<int, List<int>>(); int index = 0; for (int i = 0; i < arr.Length; i++) { index++; if (!dic.ContainsKey(arr[i])) { dic.Add(arr[i], new List<int> { index }); } else { dic[arr[i]].Add(index); } }; int minIndex = int.MaxValue; int temp=0 ; foreach(var k in dic.Keys) { if(dic[k].Count==1) { foreach(var v in dic[k]) { if (minIndex > v) { minIndex = v; temp = k; } } } } stopwatch.Stop(); Console.WriteLine(temp + "花費時間{0}",stopwatch.Elapsed.ToString());
- 快方法
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
int[] arr = { 1, 3, 5, 6, 8, 9, 10, 66, 66, 55, 88, 66, 22, 55, 581, 1};
foreach (var a in arr)
{
int firstIndex = Array.IndexOf(arr, a);
int lastIndex = Array.LastIndexOf(arr, a);
if (firstIndex == lastIndex)
{
stopwatch.Stop();
Console.WriteLine(a + "花費時間{0}", stopwatch.Elapsed.ToString());
break;
}
}
合併兩個有序陣列
- 快方法
int[] arr1 = { 1, 3, 5, 6, 8, 9, 10, 66, 66, 55, 88, 66, 22, 55, 581, 1};
int[] arr2 = { 1,4, 5,7, 8, 55, 10, 66, 66,};
List<int> list = new List<int>();
list.AddRange(arr1);
list.AddRange(arr2);
list.Sort();
foreach(var l in list)
{
Console.WriteLine(l);
}
棧
使用棧計算字尾表示式
字尾表示式簡介
中綴表示式:
通常,算術表示式寫作中綴表示式,,什麼是中綴表示式呢?中綴表示式就是:操作符位於運算元之間。如下形式: <運算元> <操作符> <運算元> 例如表示式:1+2*3, 計算時,我們根據表示式的優先規則來計算。其結果是7而不是9。 運算元> 操作符> 運算元>- 字尾表示式:
字尾表示式就是:操作符位於兩個運算元之後,字尾表示式的形式如下: <運算元> <運算元> <操作符> 。如下所示: 1 2 - 等價於1-2 操作符> 運算元> 運算元>
- 優點
使用字尾表示式的優點:字尾表示式比中綴表示式更容易計算,因為它不用考慮優先規則和括弧,表示式中的運算元和操作符的順序就足以完成計算。因此程式設計語言編輯器和執行時環境在其內部中往往使用字尾表示式。棧是用於計算字尾表示式的理想資料結構。
程式碼有點長,已經經過測試,放上跟棧有關的核心程式碼段,如下:
public int evaluate(String expr)
{
int op1, op2, result = 0;
String token;
//將字串分解,/s 匹配任何空白字元,包括空格、製表符、換頁符等。
String[] tokenizer = expr.Split(" ");
for (int x = 0; x < tokenizer.Length; x++)
{
Console.WriteLine(tokenizer[x] + " ");//輸出
token = tokenizer[x];
if (isOperator(token))
{//判斷是操作符,則出棧兩個運算元
op2 = stack.Pop();
op1 = stack.Pop();
result = evalSingleOp(token[0], op1, op2);//計算結果
stack.Push(result);//把計算結果壓入棧中
}
else
{
stack.Push( int.Parse(token));//壓入運算元
}
}
return result;
}
private bool isOperator(String token)
{
return (token.Equals("+") || token.Equals("-") ||
token.Equals("*") || token.Equals("/"));
}
對棧的元素進行排序
Stack<int> arr1 = new Stack<int>();
arr1.Push(9);
arr1.Push(3);
arr1.Push(4);
arr1.Push(7);
arr1.Push(2);
public Stack<int> StackSort(Stack<int> arr)
{
if (arr.Count==0)
{
return new Stack<int>();
}
Stack<int> newStack = new Stack<int>();
int top = arr.Pop();
newStack.Push(top);
while (arr.Count>0)
{
int first = arr.Pop(); //拿出第一個
while (newStack.Count > 0 && first > newStack.Peek())
{
int temp = newStack.Pop();
arr.Push(temp);
}
newStack.Push(first);
}
while(newStack.Count>0)
{
int temp = newStack.Pop();
arr.Push(temp);
}
return arr;
}
判斷表示式是否括號平衡
設計一個演算法,判斷使用者輸入的表示式中括號是否匹配,表示式中可能含有圓括號、中括號和大括號。
bool CheckBlancedParentheses(string ch)
{
char[] arr = ch.ToCharArray();
if (0 == arr.Length)
return false;
Stack<char> stack=new Stack<char>();
for(int i=0;i<arr.Length;i++)
{
if ('(' == arr[i] || '[' == arr[i] || '{' == arr[i])
stack.Push(arr[i]);
else if (')' == arr[i])
{
if (stack.Count == 0)
return false;
else if ('(' != stack.Peek())
return false;
else stack.Pop();
}
else if (']' == arr[i])
{
if (stack.Count == 0)
return false;
else if ('[' != stack.Peek())
return false;
else stack.Pop();
}
else if ('}' == arr[i])
{
if (stack.Count== 0)
return false;
else if ('{' != stack.Peek())
return false;
else stack.Pop();
}
}
if (stack.Count>0)
return true;
return false;
}
使用棧實現佇列
Stack stack1 = new Stack();
Stack stack2 = new Stack();
public void Push(Object o)
{
stack1.Push(o);
}
public Object Pop()
{
Object o = null;
if (stack2.Count == 0)
{
//把stack1的資料放入stack2,留下最後一個數據
while (stack1.Count > 1)
{
stack2.Push(stack1.Pop());
}
if (stack1.Count == 1)
{
//把stack1的留下的那個資料返回出去
o = stack1.Pop();
}
}
else
{
o = stack2.Pop();
}
return o;
}
佇列
使用隊列表示棧
Queue<int> queue1 = new Queue<int>();
Queue<int> queue2 = new Queue<int>();
public void Push(int o)
{
queue1.Enqueue(o);
}
public int Pop()
{
int num = 0;
while (queue1.Count > 1)
{
queue2.Enqueue(queue1.Dequeue());
}
if (queue1.Count == 1)
{
//把queue1的留下的那個資料返回出去
num = queue1.Dequeue();
while (queue2.Count > 0)
{
queue1.Enqueue(queue2.Dequeue());
}
}
return num;
}
對佇列的前k個元素倒序
public Queue<int> ReversalQueue(Queue<int> queue ,int k)
{
Queue<int> queue2 = new Queue<int>();
if (queue.Count == 0) return queue2;
Stack<int> stack = new Stack<int>();
for(int i=0;i<k;i++)
{
stack.Push(queue.Dequeue());
}
while(stack.Count>0)
{
queue2.Enqueue(stack.Pop());
}
while(queue.Count>0)
{
queue2.Enqueue(queue.Dequeue());
}
return queue2;
}
連結串列
反轉連結串列
放上核心程式碼,感興趣的可以在部落格園裡面搜一下,很多博友有介紹
public LinkNode<T> Reverse(LinkNode<T> node1, LinkNode<T> node2)
{
bool head= false;
if (node1 == this.Head) head = true;
LinkNode<T> tmp = node2.Next;
node2.Next = node1;
if (head) node1.Next = null;
if (tmp == null) {
return node2; }
else
{
return Reverse(node2, tmp);
}
}
連結串列是否有迴圈
涉及到指標
- 單鏈表判斷是否存在迴圈,即判斷是否有兩個指標指向同一位置,即判斷海量指標中是否有相同資料。然後對所有指標選擇插入排序或者快速排序。
- 將所有的遍歷過的節點用雜湊表儲存起來,用節點的記憶體地址作為雜湊表的值儲存起來。每遍歷一個節點,都在這個結構中查詢是否遍歷過。如果找到有重複,則說明該連結串列存在迴圈。如果直到遍歷結束,則說明連結串列不存在迴圈。雜湊表中儲存的值為節點的記憶體地址,這樣查詢的操作所需時間為O(1),遍歷操作需要O(n),hash表的儲存空間需要額外的O(n)。所以整個演算法的時間複雜度為O(n),空間複雜度為O(n)。
樹
找到使用地方再回頭來補,或者看上一篇文章,連結中是其他博友的介紹並附有程式碼
圖
找到使用地方再回頭來補,或者看上一篇文章,連結中是其他博友的介紹並附有程式碼
字典樹
找到使用地方再回頭來補,或者看上一篇文章,連結中是其他博友的介紹並附有程式碼
雜湊表
Dictionary的內部實現機制,Dictionary如何實現快速查詢
先看原始碼
// buckets是雜湊表,用來存放Key的Hash值
// entries用來存放元素列表
// count是元素數量
private void Insert(TKey key, TValue value, bool add)
{
if (key == null)
{
throw new ArgumentNullException(key.ToString());
}
// 首先分配buckets和entries的空間
if (buckets == null) Initialize(0);
int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; // 計算key值對應的雜湊值(HashCode)
int targetBucket = hashCode % buckets.Length; // 對雜湊值求餘,獲得需要對雜湊表進行賦值的位置
#if FEATURE_RANDOMIZED_STRING_HASHING
int collisionCount = 0;
#endif
// 處理衝突的處理邏輯
for (int i = buckets[targetBucket]; i >= 0; i = entries[i].next)
{
if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key))
{
if (add)
{
throw new ArgumentNullException();
}
entries[i].value = value;
version++;
return;
}
#if FEATURE_RANDOMIZED_STRING_HASHING
collisionCount++;
#endif
}
int index; // index記錄了元素在元素列表中的位置
if (freeCount > 0)
{
index = freeList;
freeList = entries[index].next;
freeCount--;
}
else
{
// 如果雜湊表存放雜湊值已滿,則重新從primers陣列中取出值來作為雜湊表新的大小
if (count == entries.Length)
{
Resize();
targetBucket = hashCode % buckets.Length;
}
// 大小如果沒滿的邏輯
index = count;
count++;
}
// 對元素列表進行賦值
entries[index].hashCode = hashCode;
entries[index].next = buckets[targetBucket];
entries[index].key = key;
entries[index].value = value;
// 對雜湊表進行賦值
buckets[targetBucket] = index;
version++;
#if FEATURE_RANDOMIZED_STRING_HASHING
if(collisionCount > HashHelpers.HashCollisionThreshold && HashHelpers.IsWellKnownEqualityComparer(comparer))
{
comparer = (IEqualityComparer<TKey>) HashHelpers.GetRandomizedEqualityComparer(comparer);
Resize(entries.Length, true);
}
#endif
}
快速原因 :使用雜湊表來儲存元素對應的位置,然後我們可以通過雜湊值快速地從雜湊表中定位元素所在的位置索引,從而快速獲取到key對應的Value值(可以根據上一篇介紹理解)
總結
寫文章很少有完美的說法,上一篇文章在釋出之後,我又新增修改了很多東西,有點"縫縫補補又三年"的感覺。這篇(下)也需要再進行遺漏查缺,哪天來想法了(例如雜湊,二叉樹,B樹),又來進行完善。如果覺的可以,請關注一下。 寫博文的主要目的是完善鞏固自己的知識體系,翻閱大量文章來學習的一個過程,目的並不是為了讚賞,不信你看看讚賞,是否看到了信仰。 該系列上中下基本終於結束,對於大神來說,資料結構就是小菜一碟(資料結構也不止我寫的這麼點),但對很多來人,之前對於資料結構的3W都沒怎麼花心思去想,如果有人問到了,是不是很慚愧。接下來,我會繼續鞏固基礎(這個個人覺的非常重要)和研究框架以及微服務,繼續努力!