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

有了前面两篇博文做积淀,这篇博文该干啥呢,该玩一玩Code了。以下跟第一篇的面试题对应,如果问到你,你该怎么做呢?

跟上一篇《数据结构,你还记得吗(中)》目录进行一一对应,以此来提升理解。

数组

数组反转

            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都没怎么花心思去想,如果有人问到了,是不是很惭愧。接下来,我会继续巩固基础(这个个人觉的非常重要)和研究框架以及微服务,继续努力!

原文地址:https://www.cnblogs.com/zhan520g/p/10222894.html

时间: 2024-10-15 11:31:56

数据结构,你还记得吗(下)的相关文章

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

2000年6月,微软公司发布了一种新的编程语言C#,主要由安德斯·海尔斯伯格(Anders Hejlsberg)主持开发,它是第一个面向组件的编程语言,其源码会编译成msil(中间语言)再运行.   C#是一种安全的.稳定的.简单的.优雅的,由C和C++衍生出来的面向对象的编程语言.它在继承C和C++强大功能的同时去掉了一些它们的复杂特性(例如没有宏以及不允许多重继承).C#综合了VB简单的可视化操作和C++的高运行效率,以其强大的操作能力.优雅的语法风格.创新的语言特性和便捷的面向组件编程的支

还记得面试时被算法支配的恐惧吗?

还记得面试时被算法支配的恐惧吗? <font size = '5'>?<center>面试造火箭,上班拧螺丝</center> </font> 大多数程序员心里会想"总结的真精辟",当面试到算法时,各种"跪"."再跪"."还是跪"......,多少人因为算法而拿不到心仪的offer,算法毁一生啊. 智力面试时代 现在算法已经成为大厂面试的重中之重,甚至一些国外的大厂只面试算法,为

还记得早起偷菜!朋友圈晒步?蓝鲸游戏背后的极端强迫症

据媒体报道,为期50天.以"做任务"形式诱导参与者完成各类自残行为甚至自杀的死亡游戏"蓝鲸"近期已传入国内社交平台,到5月末,已有极少数少年深陷游戏中,不能自拔.更有一些无良之人,开始借青少年对"蓝鲸"游戏的好奇心行诈骗之实.比如,借"带人进蓝鲸游戏真群"之名骗取女性用户裸照,随即以"不给钱就公开裸照"等威胁手段向女性用户索要钱财. 文/张书乐 人民网.人民邮电报专栏作者,著有<微博运营完全自学手册&

《C#编程风格》还记得多少

<C#编程风格>还记得多少 开始实习之后,才发现自己是多么地菜.还有好多东西还要去学习. 公司很好,还可以帮你买书.有一天随口问了一下上司D,代码规范上面有什么要求.然后D在Amazon上面找到了这本书<C#编程风格(The Elements of C# Style)>(中英对照),让我直接买下开看,按上面的要求编写就可以了.书可以找秘书F去报销. 上个星期四在Amazon下单,周一才到.这书确实来的有点慢,没关系,我看的快.从周一到周五,用每天上下班在挤地铁(广州地铁你懂的)的时

还记得那种 喜欢到不行的感觉么?

今天 , 听人说 . 那种喜欢到不行的 感觉 .突然感到好心酸 .喜欢到不行的那种感觉是什么 ,就是可以为了他 不给自己留余地 . 不会再让别人进入到自己的心里.有种冲动 想好好跟他在一起, 甚至一生一世 .当然, 一生一世这种事情 谁也说不准 ,但是 至少那一刻是认真的 . 没有一点欺骗 .如今的爱情都是有模式的,今儿认识了,互相觉得人还不错,例如性格够正常,都长得还行,比如个头儿符合我的标准,还有家也是一个地方的,以后不用嫁的很远.工作稳定,年龄相当.不是悸动,不是心动,更没心跳加速,只是觉

Java 8?还记得那年大明湖畔的Java 7吗?

译注:但见新人笑,哪闻旧人哭.在大家都在兴致勃勃的讨论Java 8的时候,那个早被遗忘的Java 7,或许你从来都没有记得它的好. Java 8的发布也有一个月了,我相信现在大家都在探索JDK 8中的新特性.但是,在你彻底开始钻研Java 8之前,最好先来回顾下Java 7有哪些新特性.如果你还记得的话,Java 6是没有增加任何特性的,只是JVM的一些改动以及性能的提升,不过JDK 7倒是增加了不少有助于提升开发效率的很给力的特性.我现在写这篇文章的目的是什么呢?为什么别人都在讨论Java 8

推荐一些socket工具,TCP、UDP调试、抓包工具. 还记得我在很久很久以前和大家推荐的Fiddler和Charles debugger么?他们都是HTTP的神器级调试工具,非常非常的好用。好工具

还记得我在很久很久以前和大家推荐的Fiddler和Charles debugger么?他们都是HTTP的神器级调试工具,非常非常的好用.好工具能让你事半功倍,基本上,我是属于彻头彻尾的工具控. 假如有一天,你写"传统"的PHP有些累了,想玩玩socket了,搞搞python.NodeJS.GO之类的新兴语言或框架(当然我不是说这些语言不能写web),或者干脆就用PHP吧,事实上PHP5.4的性能提高的真是相当之多,用PHP 的socket函数就能简单的写一个web socket服务器

还记得那年的QQ空间吗?

摘要:还记得初.高中时代偷偷跑出学校去网吧上网吗?还记得第一次使用QQ与陌生人聊天的感觉吗?还记得第一次在QQ空间分享自己的感受吗?你,还记得那年的QQ空间吗? 还记得那年的QQ空间吗?相信很多人的答案是"Yes",但是,仔细想一下,你真的没有忽略它吗? QQ空间于2005年诞生,与Facebook很相似.起初,它在大批同学.好友中广泛流传,用户可以在空间中查看好友的日志.状态,还可以与空间主人进行互动,体验很酷.在那个年代,由于PC普及率并不高,很多上网行为都是在网吧,显然,QQ空间

还记得大明湖畔的Moto 360吗?它降价了!

现在在智能硬件领域,Apple Watch一天又一天地刷我们的屏,你是否还记得大明湖畔的Moto 360呢? Moto 360可以说是含着金汤匙出生的,2014年9月5日正式发布,硬件是纯正的摩托罗拉血统,软件使用了谷歌专门为智能手表开发的Android Wear操作系统.Moto 360骨子里透着高贵,出生没多久就摘得了14年最佳可穿戴移动技术桂冠,同时也受到很多用户的追捧. 现在Moto 360几乎被Apple Watch夺走了所有的风光,最近从谷歌商店的官网上显示,原价249.99美元(折