09 栈与递归

知识结构:

栈是我们经常使用的一种数据结构,比如,手枪发射子弹的顺序与子弹压入弹夹的顺序是相反,即后压入弹夹的子弹先发射出来。又比如,我们使用的Word、Excel、Photoshop等软件系统中的撤销操作,也是栈的具体应用,最后做的操作,一定是最先撤销的。

下面我们就来详细介绍“栈”这种数据结构。


1. 栈的定义与操作

1.1 栈的定义

插入(入栈)和删除(出栈)操作只能在一端(栈顶)进行的线性表。即先进后出(First In Last Out)的线性表。

例1 :线性表$(a_0,a_1,⋯,a_{n-1)}$进栈与出栈演示。

1.2 栈的操作

  • 入栈操作:将数据元素插入栈顶。
  • 出栈操作:移除栈顶的数据元素。
  • 是否为空:判断栈中是否包含数据元素。
  • 得到栈深:获取栈中实际包含数据元素的个数。
  • 清空操作:移除栈中的所有数据元素。
  • 获取栈顶元素。

using System;namespace LinearStruct
{/// <summary>/// 栈的抽象数据类型/// </summary>/// <typeparam name="T">栈中元素的类型</typeparam>public interface IStack<T> where T : IComparable<T>{/// <summary>/// 获取栈中实际包含元素的个数/// </summary>int Length { get; }/// <summary>/// 获取栈顶元素/// </summary>T StackTop { get; }/// <summary>/// 数据元素入栈/// </summary>/// <param name="data">要入栈的元素</param>void Push(T data);/// <summary>/// 数据元素出栈/// </summary>void Pop();/// <summary>/// 判断栈中是否包含元素/// </summary>/// <returns>如果包含元素返回false,否则返回true.</returns>bool IsEmpty();/// <summary>/// 从栈中移除所有元素/// </summary>void Clear();}
}

2. 栈的顺序存储(顺序栈)

顺序栈:利用顺序表实现的栈。

实现:

using System;namespace LinearStruct
{/// <summary>/// 用顺序存储结构实现的栈/// </summary>/// <typeparam name="T">顺序栈中元素的类型</typeparam>public class SeqStack<T> : IStack<T> where T : IComparable<T>{private readonly SeqList<T> _lst;/// <summary>/// 初始化SeqStack类的新实例/// </summary>/// <param name="max">SeqStack中最多包含元素的个数</param>public SeqStack(int max){if (max <= 0)throw new ArgumentOutOfRangeException();_lst = new SeqList<T>(max);}/// <summary>/// 获取SeqStack中实际包含元素的个数/// </summary>public int Length{get { return _lst.Length; }}/// <summary>/// 获取SeqStack中最多包含元素的个数/// </summary>public int MaxSize{get { return _lst.MaxSize; }}/// <summary>/// 获取SeqStack中的栈顶元素/// </summary>public T StackTop{get{if (_lst.IsEmpty())throw new Exception("栈为空.");return _lst[0];}}/// <summary>/// 数据元素入栈/// </summary>/// <param name="data">要入栈的元素</param>public void Push(T data){if (_lst.Length == _lst.MaxSize)throw new Exception("栈已达到最大容量.");_lst.Insert(0, data);}/// <summary>/// 数据元素出栈/// </summary>public void Pop(){if (_lst.IsEmpty())throw new Exception("栈为空.");_lst.Remove(0);}/// <summary>/// 判断SeqStack中是否包含元素/// </summary>/// <returns>如果包含元素返回false,否则返回true.</returns>public bool IsEmpty(){return _lst.IsEmpty();}/// <summary>/// 从SeqStack中移除所有元素/// </summary>public void Clear(){_lst.Clear();}}
}

应用:

using System;
using LinearStruct;namespace ExampleStack
{class Program{static void Main(string[] args){StackTest(new SeqStack<string>(20));}private static void StackTest(IStack<string> stack){stack.Push("a1");stack.Push("a2");stack.Push("a3");while (stack.IsEmpty() == false){Console.WriteLine(stack.StackTop);stack.Pop();}// a3// a2// a1}}
}

SeqStack<T>虽然实现了IStack<T>接口,但在入栈和出栈时,会移动大量的元素,效率比较低。故把入栈和出栈放到顺序表的尾部进行,这样可以提升效率。

  • 入栈:Insert(Length, data)
  • 出栈:Remove(Length - 1)
  • 栈顶元素:SeqList[Length - 1]
using System;namespace LinearStruct
{/// <summary>/// 用顺序存储结构实现的栈/// </summary>/// <typeparam name="T">顺序栈中元素的类型</typeparam>public class SeqStack_1<T> : IStack<T> where T : IComparable<T>{private readonly SeqList<T> _lst;/// <summary>/// 初始化SeqStack类的新实例/// </summary>/// <param name="max">SeqStack中最多包含元素的个数</param>public SeqStack_1(int max){if (max <= 0)throw new ArgumentOutOfRangeException();_lst = new SeqList<T>(max);}/// <summary>/// 获取SeqStack中实际包含元素的个数/// </summary>public int Length{get { return _lst.Length; }}/// <summary>/// 获取SeqStack中最多包含元素的个数/// </summary>public int MaxSize{get { return _lst.MaxSize; }}/// <summary>/// 获取SeqStack中的栈顶元素/// </summary>public T StackTop{get{if (_lst.IsEmpty())throw new Exception("栈为空.");return _lst[Length - 1];}}/// <summary>/// 数据元素入栈/// </summary>/// <param name="data">要入栈的元素</param>public void Push(T data){if (_lst.Length == _lst.MaxSize)throw new Exception("栈已达到最大容量.");_lst.Insert(Length, data);}/// <summary>/// 数据元素出栈/// </summary>public void Pop(){if (_lst.IsEmpty())throw new Exception("栈为空.");_lst.Remove(Length - 1);}/// <summary>/// 判断SeqStack中是否包含元素/// </summary>/// <returns>如果包含元素返回false,否则返回true.</returns>public bool IsEmpty(){return _lst.IsEmpty();}/// <summary>/// 从SeqStack中移除所有元素/// </summary>public void Clear(){_lst.Clear();}}
}

应用:

using System;
using LinearStruct;namespace ExampleStack
{class Program{static void Main(string[] args){StackTest(new SeqStack_1<string>(20));}private static void StackTest(IStack<string> stack){stack.Push("a1");stack.Push("a2");stack.Push("a3");while (stack.IsEmpty() == false){Console.WriteLine(stack.StackTop);stack.Pop();}// a3// a2// a1}}
}

3. 栈的链式存储(链栈)

链栈:利用单链表实现的栈。

实现:

using System;namespace LinearStruct
{/// <summary>/// 用链式存储结构实现的栈/// </summary>/// <typeparam name="T">栈中元素的类型</typeparam>public class LinkStack<T> : IStack<T> where T : IComparable<T>{private readonly SLinkList<T> _lst;/// <summary>/// 初始化LinkStack类的新实例/// </summary>public LinkStack(){_lst = new SLinkList<T>();}/// <summary>/// 获取LinkStack中实际包含元素的个数/// </summary>public int Length{get { return _lst.Length; }}/// <summary>/// 获取LinkStack中的栈顶元素/// </summary>public T StackTop{get{if (_lst.Length == 0)throw new Exception("栈为空.");return _lst[0];}}/// <summary>/// 数据元素入栈/// </summary>/// <param name="data">要入栈的元素</param>public void Push(T data){_lst.InsertAtFirst(data);}/// <summary>/// 数据元素出栈/// </summary>public void Pop(){if (_lst.Length == 0)throw new Exception("栈为空.");_lst.Remove(0);}/// <summary>/// 判断LinkStack中是否包含元素/// </summary>/// <returns>如果包含元素返回false,否则返回true.</returns>public bool IsEmpty(){return _lst.IsEmpty();}/// <summary>/// 从LinkStack中移除所有元素/// </summary>public void Clear(){_lst.Clear();}}
}

应用:

using System;
using LinearStruct;namespace ExampleStack
{class Program{static void Main(string[] args){StackTest(new LinkStack<string>());}private static void StackTest(IStack<string> stack){stack.Push("a1");stack.Push("a2");stack.Push("a3");while (stack.IsEmpty() == false){Console.WriteLine(stack.StackTop);stack.Pop();}// a3// a2// a1}}
}

4. 栈的应用

4.1 问题描述

假设一列货运列车共有n节车厢,每节车厢将停放在不同的车站。假定n个车站的编号分别为1至n,货运列车按照第n站至第1站的次序经过这些车站。车厢的编号与它们的目的地相同。为了便于从列车上卸掉相应的车厢,必须重新排列车厢,使各车厢从前至后按编号1至n的次序排列。当所有的车厢都按照这种次序排列时,在每个车站只需卸掉最后一节车厢即可。

我们在一个转轨站里完成车厢的重排工作,在转轨站中有一个入轨、一个出轨和k个缓冲铁轨(位于入轨和出轨之间)。图1(a)给出一个转轨站,其中有k个(k=3)缓冲铁轨H1H2H3。开始时,n节车厢的货车从入轨处进入转轨站,转轨结束时各车厢从右到左按照编号1至n的次序离开转轨站(通过出轨处)。在图1(a)中,n=9,车厢从后至前的初始次序为5,8,1,7,4,2,9,6,3。图1(b)给出了按所要求的次序重新排列后的结果。

4.2 问题分析

现在分析图1,为了重排车厢,需从前至后依次检查入轨上的所有车厢。如果正在检查的车厢就是下一个满足排列要求的车厢,可以直接把它放到出轨上去。如果不是,则把它移动到缓冲铁轨上,直到按输出次序要求轮到它时才将它放到出轨上。缓冲铁轨上车厢的进和出只能在缓冲铁轨的尾部进行。在重排车厢过程中,仅允许以下移动:

1、车厢可以从入轨移动到一个缓冲铁轨的尾部或进入出轨接在重排的列车后。

2、车厢可以从一个缓冲铁轨的尾部移动到出轨接在重排的列车后。

考察图1(a),3号车厢在入轨的前部,但由于它必须位于1号和2号车厢的后面,因此不能立即输出3号车厢,可把3号车厢送入缓冲铁轨H1。下一节车厢是6号车厢,也必须送入缓冲铁轨。如果把6号车厢送入H1,那么重排过程将无法完成,因为3号车厢位于6号车厢的后面,而按照重排的要求,3号车厢必须在6号车厢之前输出。因此可把6号车厢送入H2。下一节车厢是9号车厢,被送入H3,因为如果把它送入H1H2,重排过程也将无法完成。至此,缓冲铁轨的当前状态如图2(a)所示。

接下来处理2号车厢,它可以被送入任一个缓冲铁轨,因为它能满足缓冲铁轨上车厢编号必须递增排列的要求,不过,应优先把2号车厢送入H1,因为如果把它送入H3,将没有空间来移动7号车厢和8号车厢。如果把2号车厢送入H2,那么接下来的4号车厢必须被送入H3,这样将无法移动后面的5号、7号和8号车厢。新的车厢u应送入这样的缓冲铁轨:其底部的车厢编号v满足v > u,且v是所有满足这种条件的缓冲轨顶部车厢编号中最小的一个编号。只有这样才能使后续的车厢重排所受到的限制最小

接下来处理4号车厢时,三个缓冲铁轨顶部的车厢分别为2号、6号和9号车厢。根据分配规则,4号车厢应送入H2。这之后,7号车厢被送入H3。图2(b)给出了当前的状态。

接下来,1号车厢被送至出轨,这时,可以把H1中的2号车厢送至出轨。之后,从H1输出3号车厢, 输出4号车厢。至此,没有可以立即输出的车厢了。接下来的8号车厢被送入H1,然后5号车厢从入轨处直接输出到出轨处。这之后,从H2输出6号车厢,从H3输出7号车厢,从H1输出8号车厢,最后从H3输出9号车厢。

到此为止,整个“入轨,出轨”完毕!让我们整理一下算法思路:

  • 第一步:若需出轨的编号恰是入轨编号,则直接出轨。
  • 第二步:若需出轨的编号恰是缓冲轨的最小编号,则直接出轨。
  • 第三步:将入轨编号放入缓冲轨。(规则:放到满足小于缓冲轨栈顶元素编号且栈顶元素最小的上面。)

4.3 参考代码

using System;
using LinearStruct;namespace TrainArrange
{class Program{/// <summary>/// 车厢重排算法/// </summary>/// <param name="p">入轨序列</param>/// <param name="k">缓冲轨条数</param>/// <returns>重排是否成功</returns>static bool RailRoad(int[] p, int k){LinkStack<int>[] h = new LinkStack<int>[k];for (int i = 0; i < h.Length; i++)h[i] = new LinkStack<int>();int nowOut = 1; //下一次要输出的车厢号int minH = int.MaxValue; //缓冲铁轨中编号最小的车厢int minS = -1; //minH号车厢对应的缓冲铁轨for (int i = 0; i < p.Length; i++){if (p[i] == nowOut){Console.WriteLine("移动车厢:{0}从入轨到出轨。", p[i]);nowOut++;//从缓冲铁轨中输出while (minH == nowOut){Output(ref minH, ref minS, h); //出轨nowOut++;}}else{//将p[i]送入某个缓冲铁轨if (Input(p[i], ref minH, ref minS, h) == false){return false;}}}return true;}/// <summary>/// 从缓冲轨移除车厢出轨/// </summary>/// <param name="minH">缓冲铁轨中编号最小的车厢</param>/// <param name="minS">minH号车厢对应的缓冲铁轨</param>/// <param name="h">缓冲轨道的集合</param>static void Output(ref int minH, ref int minS, LinkStack<int>[] h){h[minS].Pop(); //从堆栈minS中删除编号最小的车厢minHConsole.WriteLine("移动车厢:{0}从缓冲轨{1}到出轨。", minH, minS);//通过检查所有的栈顶,搜索新的minH和minSminH = int.MaxValue;minS = -1;for (int i = 0; i < h.Length; i++){if (h[i].IsEmpty() == false && h[i].StackTop < minH){minH = h[i].StackTop;minS = i;}}}/// <summary>/// 在一个缓冲铁轨中放入车厢c/// </summary>/// <param name="c">放入车厢编号</param>/// <param name="minH">栈顶编号的最小值</param>/// <param name="minS">栈顶编号最小值所在堆栈的编号</param>/// <param name="h">缓冲轨道的集合</param>/// <returns>如果没有可用的缓冲铁轨,则返回false,否则返回true。</returns>static bool Input(int c, ref int minH, ref int minS, LinkStack<int>[] h){int bestTrack = -1; //目前最优的铁轨int bestTop = int.MaxValue; //最优铁轨上的头辆车厢for (int i = 0; i < h.Length; i++){if (h[i].IsEmpty() == false){int x = h[i].StackTop;if (c < x && x < bestTop){bestTop = x;bestTrack = i;}}else{if (bestTrack == -1){bestTrack = i;break;}}}if (bestTrack == -1)return false;h[bestTrack].Push(c);Console.WriteLine("移动车厢:{0}从入轨到缓冲轨{1}。", c, bestTrack);if (c < minH){minH = c;minS = bestTrack;}return true;}static void Main(string[] args){int[] p = new int[] { 3, 6, 9, 2, 4, 7, 1, 8, 5 };int k = 3;bool result = RailRoad(p, k);do{if (result == false){Console.WriteLine("需要更多的缓冲轨道,请输入需要添加的数量:");k = k + Convert.ToInt32(Console.ReadLine());result = RailRoad(p, k);}} while (result == false);}}
}

4.4 输出结果


5. 递归

如果一个函数在内部调用自身本身,这个函数就是递归函数。

【例1】:求n的阶乘

n! = 1 x 2 x 3 x ... x n

循环:

static int factorial(int n)
{if (n < 1)throw new ArgumentOutOfRangeException();int result = n;for (int i = 1; i < n; i++){result *= i;}return result;
}static void Main(string[] args)
{Console.WriteLine(factorial(5));//120
}

递归:

static int factorial(int n)
{if (n < 1)throw new ArgumentOutOfRangeException();if (n == 1)return 1;return n * factorial(n - 1);
}static void Main(string[] args)
{Console.WriteLine(factorial(5));//120
}

【例2】:斐波那契数列

f(n)=f(n-1)+f(n-2), f(0)=0 f(1)=1

循环:

static int[] recur_fibo(int n)
{if (n < 2)throw new ArgumentOutOfRangeException();int[] result = new int[n];result[0]= 0;result[1] = 1;for (int i = 2; i < n; i++){result[i] = result[i - 1] + result[i - 2];}return result;
}static void Main(string[] args)
{int[] r = recur_fibo(11);for (int i = 0; i < r.Length; i++){Console.Write(r[i]+" ");}Console.WriteLine();// 0 1 1 2 3 5 8 13 21 34 55
}

递归:

static int recur_fibo(int n)
{if (n <= 1)return n;return recur_fibo(n - 1) + recur_fibo(n - 2);
}static void Main(string[] args)
{int[] result = new int[11];for (int i = 0; i < result.Length; i++){result[i] = recur_fibo(i);Console.Write(result[i] + " ");}Console.WriteLine();// 0 1 1 2 3 5 8 13 21 34 55
}

【例3】:汉诺塔问题

汉诺塔问题源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着 64 片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。

如果我们要思考每一步怎么移可能会非常复杂,但是可以将问题简化。

我们可以先假设除 a 柱最下面的盘子之外,已经成功地将 a 柱上面的 63个盘子移到了 b 柱,这时我们只要再将最下面的盘子由 a 柱移动到 c 柱即可。

当我们将最大的盘子由 a 柱移到 c 柱后,b 柱上便是余下的 63 个盘子,a 柱为空。因此现在的目标就变成了将这 63 个盘子由 b 柱移到 c 柱。这个问题和原来的问题完全一样,只是由 a 柱换为了 b 柱,规模由 64 变为了 63。因此可以采用相同的方法,先将上面的 62 个盘子由 b 柱移到 a 柱,再将最下面的盘子移到 c 柱。

以此内推,再以 b 柱为缓冲,将 a 柱上面的 62 个圆盘最上面的 61 个圆盘移动到 b 柱,并将最后一块圆盘移到 c 柱。

我们已经发现规律,我们每次都是以 a 或 b 中一根柱子为缓冲,然后先将除了最下面的圆盘之外的其它圆盘移动到辅助柱子上,再将最底下的圆盘移到 c 柱子上,不断重复此过程。

这个反复移动圆盘的过程就是递归,例如我们每次想解决 n 个圆盘的移动问题,就要先解决(n-1)个盘子进行同样操作的问题。

于是可以编写一个函数,move(n, a, b, c)。可以这样理解:move(盘子数量, 起点, 缓冲, 终点)。

1. a 上只有一个盘子的情况,直接搬到 c,代码如下:

if(n == 1)Console.WriteLine(a, '-->', c)

2. a 上不止有一个盘子的情况:

首先,需要把 n-1 个盘子搬到 b 柱子缓冲。打印出的效果是:a --> b。

move(n - 1, a, c, b)

再把最大的盘子搬到 c 柱子,也是最大尺寸的一个。打印出:a–>c。

move(1, a, b, c)

最后,把剩下 b 柱的 n-1 个盘子搬到 c 上,此时缓冲变成了起点,起点变成了缓冲。

move(n - 1, b, a, c)

利用 C# 实现汉诺塔问题

class Program
{private static int i = 0;static void Move(int n, string a, string b, string c){if (n == 1){Console.WriteLine("移动第 {0} 次 {1}-->{2}", ++i, a, c);return;}Move(n - 1, a, c, b);Move(1, a, b, c);Move(n - 1, b, a, c);}static void Main(string[] args){Move(3, "a", "b", "c");}
}
// 移动第 1 次 a --> c
// 移动第 2 次 a --> b
// 移动第 3 次 c --> b
// 移动第 4 次 a --> c
// 移动第 5 次 b --> a
// 移动第 6 次 b --> c
// 移动第 7 次 a --> c

后台回复「搜搜搜」,随机获取电子资源!
欢迎关注,请扫描二维码:



数据结构与算法:09 栈与递归相关推荐

  1. C++数据结构和算法2 栈 双端/队列 冒泡选择插入归并快排 二三分查找 二叉树 二叉搜索树 贪婪 分治 动态规划

    C++数据结构和算法2 栈 双端/队列 冒泡选择插入归并快排 二三分查找 二叉树 二叉搜索树 贪婪 分治 动态规划 博文末尾支持二维码赞赏哦 _ github 章3 Stack栈 和 队列Queue= ...

  2. 数据结构与算法之栈入门题目

    数据结构与算法之栈题目 目录 用数组实现大小固定的队列和栈 实现一个特殊的栈,在实现栈的基础功能上,再实现返回栈中最小元素的操作 如果仅用栈结构实现队列结构和如何仅用队列结构实现栈结构 1. 用数组实 ...

  3. 数据结构与算法--利用栈实现队列

    利用栈实现队列 上一节中说明了栈的特点 后进先出,我们用数组的方式实现了栈的基本操作api,因此我们对栈的操作是不考虑排序的,每个api的操作基本都是O(1)的世界,因为不考虑顺序,所以找最大,最小值 ...

  4. 数据结构与算法--简单栈实现及其应用

    栈 栈(Stack)是一种限制插入和删除只能在一个位置上进行的表,改位置是表的末端,叫做栈顶top.栈的基本操作有push (进栈)pop(出栈) 栈又叫做LIFO(后进先出)表,下图展示普通push ...

  5. 新星计划Day7【数据结构与算法】 栈Part1

    新星计划Day7[数据结构与算法] 栈Part1

  6. 【数据结构与算法】栈与队列

    栈 一.什么是栈? 1.后进者先出,先进者后出,这就是典型的"栈"结构. 2.从栈的操作特性来看,是一种"操作受限"的线性表,只允许在端插入和删除数据. 二.为 ...

  7. 数据结构与算法之-----栈的应用(三)

    [ 写在前面的话:本专栏的主要内容:数据结构与算法. 1.对于​​​​​​​初识数据结构的小伙伴们,鉴于后面的数据结构的构建会使用到专栏前面的内容,包括具体数据结构的应用,所使用到的数据结构,也是自己 ...

  8. 数据结构与算法之-----栈的应用(一)

    [ 写在前面的话:本专栏的主要内容:数据结构与算法. 1.对于​​​​​​​初识数据结构的小伙伴们,鉴于后面的数据结构的构建会使用到专栏前面的内容,包括具体数据结构的应用,所使用到的数据结构,也是自己 ...

  9. 【数据结构与算法】栈与队列【C语言版】

    目录 3.1 栈和队列的定义和特点 3.2 栈.队列与一般线性表的区别 3.3 栈的表示和操作的实现 顺序栈与顺序表 ================= 顺序栈的表示 顺序栈初始化 判断顺序栈是否为空 ...

最新文章

  1. java opcode 反汇编,OPCode详解及汇编与反汇编原理
  2. (73)分析 KeInitializeApc ,了解 KAPC 的初始化
  3. 阿里技术专家推荐的20本书,免费送!
  4. Python 函数参数传递
  5. 小程序更换域名后发现图片显示不出来的问题解决方法
  6. AOS编排语言系列教程(五):创建安全组SecurityGroup
  7. 2018-9-25 进入公司第一天
  8. Jenkinsfile脚本实现master、slave节点(agent)共享内容
  9. dbutils mysql_mysql dbutils
  10. Java程序员面试宝典笔记记录(终)-第9章海量数据部分笔记
  11. 应聘PHP有面试题吗,php应聘面试题
  12. mac系统+frida 简单测试真机or genymotion模拟器
  13. 穿行测试工作底稿 软件行业,CPA审计预习书(第5话)——风险评估工作底稿之了解被审计单位的内部控制、穿行测试和控制测试...
  14. C语言基础入门48篇_30_二维数组的定义与使用(二维数组的定义:type 数组名[行][列]、二维数组的初始化、二维数组的引用)
  15. Roblox剑九之剑一
  16. 电脑提示丢失MSVCP140.dll无法启动此程序怎么办【解决方法】
  17. 海康威视:笔试题(20190908)
  18. 计算机表格常用根式,常用平方根表.doc
  19. OTFS白皮书-翻译
  20. JDK环境变量配置环境变量版本查询

热门文章

  1. c语言多线程mysql_多线程读写mysql数据库
  2. 1081 Rational Sum 有理数类型题处理 需再做
  3. 如何提升自己的Web前端技术
  4. 自动驾驶L3量产追梦:如何跨过车规级和低成本门槛?
  5. windows下安装mysql8.0压缩版
  6. 测试服务命名和动态注册路由的方式@Xan
  7. [日常] Go语言圣经-函数递归习题
  8. Java中 与,||与|的区别
  9. Python学习day5作业-ATM和购物商城
  10. win7 64位操作系统中 Oracle 11g 安装教程(图解)