数据结构与算法之栈
前言:我也是即学即写的,难免有错误。欢迎之处这篇博文的不足之处。
一、什么是栈?
栈是数据结构的一种,他的原理就是后进先出(先进后出是一样的道理),如图(无图言屌)
看到没,你如果把栈当成一个开口向上的容器的话,你最先存储的a1要想取出来,必须把上面的元素都取出来,而你最后一个存储的a5元素却可以第一个取出来。这就是后进先出。
PS:我这里的top是指向栈顶元素的,这里用到的top初值是-1.有的书中(好像是严蔚敏的)的top的初始值是0.所以top指向栈顶元素的上一个存储单元。我个人觉得还是top初始值为-1比较好。关于这个知识点下面详讲。
二、为什么要学栈?学了有什么用?
栈是解决问题的一种方法,有些情况下用栈是非常实用的,举一些例子,比如你浏览网页,当你打开浏览器的开始,第一个肯定是首页,然后你搜索了蜀云泉,第二个肯定是搜索列表,然后你点击进入蜀云泉的博客,好了,蜀云泉的博客界面是你的第三个界面对吧。现在你点击一下浏览器的返回键,你可以看看浏览器返回的是哪个界面?没错是第二个搜索界面。这其中就用到了栈的思想。先进后出或者后进先出。
再举个例子,方法里面调用方法,我写个代码瞧瞧:
1 bool then(int item) 2 { 3 if (item > 0) 4 { 5 return true; 6 } 7 else 8 return false; 9 } 10 11 void start() 12 { 13 14 Console.WriteLine(then(5)); 15 16 }
来看start方法里面,先进入的是start方法,后进入的是then方法,但是是then方法先返回值,这还是后进先出的道理。
三、栈怎么用代码去实现?
终于到代码阶段了,栈分为顺序栈和链栈(是不是与顺序表,链表很相似?答案是相似度很高啊...)
首先来看看BCL中的栈的基本操作:BCL就是visual studio 里面封装的栈Stack。直接拿来用就好。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 6 Stack<int> stack = new Stack<int>(); //这是定义一个栈的对象stack 7 stack.Push(6); //Push就是入栈的操作,它是没有返回值的,含一个参数 8 stack.Pop(); //Pop有返回值,返回栈顶的元素并且把栈顶元素删掉,top指向下一个元素 9 stack.Peek(); //Peek有返回值,返回栈顶元素。与Pop的区别在于Peek不删除栈顶元素 10 stack.Count(); //有返回值,返回栈里面的元素个数 11 stack.Clear(); //清空栈 12 13 } 14 }
这几个是比较常见的,还有一些其他的就不多叙述了。看到这里你有可能会说既然visual studio已经封装好栈了我直接用不就得了,那我不是已经学会栈了吗,真简单。是的,你现在已经学会栈这个数据结构了,但是算法呢?Pop,Peek它们内部是怎么实现的你知道吗?如果在某个偶然的条件下,BCL类不能用了,怎么办?所以要学这个算法,至少你可以自己写一个栈,用自己的代码,让BCL凉快去吧。(了解内部的构成后,其实用BCL也没什么不对。人家visual studio封装好的不用白不用...)
顺序栈:
首先新建一个接口:
1 interface IStack<T> //这里的T是泛型的意思,就是你定义成什么类型T就是什么类型,相当于T是类型的一个代表 2 { 3 int Count{get; } 4 bool IsEmpty(); 5 int GetLength(); 6 void Push(T item); 7 T Pop(); 8 T Peek(); 9 }
然后新建一个类作为顺序栈,这个类继承接口:
1 namespace ConsoleApplication2 2 { 3 class MyStack <T>:IStack<T> 4 { 5 private T [] Data; 6 private int top; 7 8 public MyStack(int size) 9 { 10 Data = new T[size]; 11 top = -1; 12 } 13 public MyStack():this(10) 14 { 15 top = -1; 16 } 17 18 19 20 public int Count 21 { 22 get { return top + 1; } 23 } 24 25 public bool IsEmpty() 26 { 27 return Count == 0; 28 } 29 30 public int GetLength() 31 { 32 return Count; 33 } 34 35 public void Push(T item) 36 { 37 if (Count==Data.Length) 38 { 39 Console.WriteLine("栈已经满了,无法插入元素"); 40 } 41 else 42 { 43 Data[top + 1] = item; 44 top++; 45 } 46 47 } 48 49 public T Pop() 50 { 51 if (Count>0) 52 { 53 T item = Data[top]; 54 top--; 55 return item; 56 } 57 else 58 { 59 Console.WriteLine("栈是空的,无法取值"); 60 return default(T); 61 } 62 63 } 64 65 public T Peek() 66 { 67 if (Count>0) 68 { 69 return Data[top]; 70 } 71 else 72 { 73 Console.WriteLine("栈是空的,无法取值"); 74 return default(T); 75 } 76 } 77 } 78 }
完成了。在控制台里面调用一下看看行不行
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 6 //顺序栈 7 IStack<int> stack = new MyStack<int>(); 8 9 stack.Push(3); 10 stack.Push(8); 11 Console.WriteLine(stack.Count); 12 Console.WriteLine(stack.Peek()); 13 Console.WriteLine(stack.Count); 14 Console.WriteLine(stack.Pop()); 15 Console.WriteLine(stack.Count); 16 Console.ReadLine(); 17 18 } 19 }
运行结果:
链栈:
新建一个类作为链栈的节点:
1 class Node<T> 2 { 3 private T data; 4 public T Data 5 { 6 get { return data; } 7 set { data = value; } 8 } 9 10 private Node<T> next; 11 internal Node<T> Next 12 { 13 get { return next; } 14 set { next = value; } 15 } 16 17 public Node() 18 { 19 data = default(T); 20 next = null; 21 } 22 public Node(T data) 23 { 24 this.data = data; 25 next = null; 26 } 27 public Node(T data, Node<T> next) 28 { 29 this.data = data; 30 this.next = next; 31 } 32 public Node(Node<T> next) 33 { 34 this.next = next; 35 data = default(T); 36 } 37 38 39 }
新建一个类作为链栈功能的实现,还是继承那个接口:
1 namespace ConsoleApplication2 2 { 3 class ListStack<T>:IStack<T> 4 { 5 private Node<T> top; 6 private int count; 7 8 public int Count 9 { 10 get { return count; } 11 } 12 13 public bool IsEmpty() 14 { 15 return count == 0; 16 } 17 18 public int GetLength() 19 { 20 return count; 21 } 22 23 public void Push(T item) 24 { 25 Node<T> newNode = new Node<T>(item); 26 newNode.Next = top; 27 top = newNode; 28 count++; 29 } 30 31 public T Pop() 32 { 33 if (count>0) 34 { 35 T data = top.Data; 36 top = top.Next; 37 count--; 38 return data; 39 } 40 else 41 { 42 Console.WriteLine("栈是空的,无法取值"); 43 return default(T); 44 } 45 46 } 47 48 public T Peek() 49 { 50 if (count>0) 51 { 52 return top.Data; 53 } 54 else 55 { 56 Console.WriteLine("栈是空的,无法取值"); 57 return default(T); 58 } 59 } 60 } 61 }
在控制台运行试试
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 6 //链栈 7 IStack<int> stack = new ListStack<int>(); 8 stack.Push(3); 9 stack.Push(8); 10 Console.WriteLine(stack.Count); 11 Console.WriteLine(stack.Peek()); 12 Console.WriteLine(stack.Count); 13 Console.WriteLine(stack.Pop()); 14 Console.WriteLine(stack.Count); 15 Console.ReadLine(); 16 17 } 18 }
运行结果:
顺序栈和链栈都成功了。
四、重点难点详解。
top初始值为-1或者是0,这都是什么和什么啊
top初始值为-1:
当你存储元素之后,top++,这时候top一直是指向栈顶元素的,这样做的好处是,栈的个数是top,而且stack[top]直接就是栈顶元素。缺点是调用Push时要Push(++top),栈的个数也是top+1.
top初始值为0:
top初始值为0的好处呢,就是Push的话直接Push(top),然后再top++;和栈的元素个数是top。稍微麻烦的是栈顶元素是stack[top-1].
其实top的初始值是-1还是0无关紧要,按照自己的习惯来就好,我是更倾向于top=-1;
顺序栈像一个只有一端开口的数组一样,链栈是怎么回事呀?
你可以把链栈理解为一个一个节点Node链接起来了,一个节点应该包括两部分,data和next.data里面装的是本节点的数据,next里面存储的是上一个节点的
链栈是这样进行的
五:后续
看懂这篇博文之后大家只能对栈有一个大概的认识,不至于说起栈来不知所以。要想真正做到得心应手的去使用栈,还得靠大家自己去摸索。再会。