数据结构,你还记得吗(下)

有了前面两篇博文做积淀,这篇博文该干啥呢,该玩一玩Code了。下面将以《数据结构,你还记得吗(上)》里面所涉及的面试题为来源,如果问到你,你该怎么做呢?
>跟上一篇《[数据结构,你还记得吗(中)](https://www.cnblogs.com/zhan520g/p/10218104.html)》目录进行一一对应,以此来提升理解。

数组

数组反转

            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. 中缀表达式:
    通常,算术表达式写作中缀表达式,,什么是中缀表达式呢?中缀表达式就是:操作符位于操作数之间。如下形式:<操作数><操作符><操作数>例如表达式:1+2*3, 计算时,我们根据表达式的优先规则来计算。其结果是7而不是9。

  2. 后缀表达式:
    后缀表达式就是:操作符位于两个操作数之后,后缀表达式的形式如下: <操作数><操作数><操作符> 。如下所示: 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都没怎么花心思去想,如果有人问到了,是不是很惭愧。接下来,我会继续巩固基础(这个个人觉的非常重要)和研究框架以及微服务,继续努力!

posted @ 2019-01-06 01:21  天空的湛蓝  阅读(560)  评论(0编辑  收藏  举报