基本数据结构(1)——算法导论(11)

1. 引言

    从这篇博客开始,来介绍一些基本的数据结构知识。本篇及下一篇会介绍几种基本的数据结构:栈、队列、链表和有根树。此外还会介绍由数组构造对象和指针的方法。

    这一篇主要介绍栈和队列,它们都是动态集合

    从数据的逻辑结构上讲,在它们上进行delete操作所移除的元素是固定的:在栈(stack)中,被删除的是最近插入的元素(后进先出,LIFO,last-in,first-out);而在队列(queue)中,被删除的元素是最先插入的元素(先进先出,FIFO,first-in,first-out)。

    从数据的存储结构上讲,它们都可以用顺序结构存储和链式结构存储来实现(虽然在之后的图中,在形式上是以数组的方式画出的)。

2. 栈(stack)

     在栈上的insert操作被称为push(压入,压栈),而delete操作被称为pop(弹出,出栈)。在现实生活中有许多与栈类似的“结构”。如一个弹夹(后被塞入弹夹的子弹先被打出),一摞堆叠的盘子(我们每次只能取出最上面的盘子)等等。

    如下图,是一个栈S在进行push和pop操作时的示意图。栈有一个属性top(栈顶指针),用来标记(指向)栈顶元素。注意:有时候我们在实现push或者pop操作时,不一定真的要在物理存储结构上添加或移除某个元素(因为这会带来额外的开销,当然有必要做时要做),而是可以通过移动栈顶指针来实现。

    如果在栈中不包含任何元素,我们称栈是空(empty)的;如果试图对一个空栈进行pop操作,则称栈下溢(underflow);相反,如果试图对一个大小固定为n,且top已经指向了位置n的栈进行push操作时,则称栈上溢(overflow);

    下面给出实现栈的伪代码(没有考虑栈的上溢问题):

    栈的一个经典的应用就是检索一串只包含正反括号的字符串中的正反括号是否匹配(这里匹配的概念是:就像我们在写代码时,正反括号不管如何层层嵌套,都必须正反匹配)。具体的做法是:我们遍历待检索的字符串,遇到正括号就把正括号push入栈,遇到反括号就对栈进行pop操作。若在遍历过程中遇到栈下溢错误或遍历完成后栈不为空则字符串中的正反括号是不匹配的;否则是匹配的。

下面给出Java实现代码:

public class Main11 {
    public static void main(String[] args) {
        System.out.println(checkBrackets("()((())"));
        System.out.println(checkBrackets("())(())"));
        System.out.println(checkBrackets("()(())"));
    }

    /**
     * 检查str中的括号是否匹配
     *
     * @param str
     * @return
     */
    public static boolean checkBrackets(String str) {
        if (str == null || str.isEmpty()) {
            throw new NullPointerException("待检测的字符串不能为空");
        }
        if (!str.matches("[\\(\\)]+")) {
            throw new RuntimeException("待检测的字符不能包含除‘(‘,‘)‘以外的字符");
        }
        Stack<Character> stack = new Stack<Character>();
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (c == ‘(‘) {
                stack.push(c);
            } else {
                try {
                    stack.pop();
                } catch (Exception e) {
                    return false;
                }
            }
        }
        return stack.isEmpty();
    }
}

/**
 * <p>
 * 栈
 * </p>
 * <p>
 * 这里只是对LinkedList作简单的封装来模拟一个栈结构
 * </p>
 *
 * @author D.K
 *
 * @param <T>
 */
class Stack<T> {
    private LinkedList<T> list;

    public Stack() {
        list = new LinkedList<>();
    }

    public boolean isEmpty() {
        return list.isEmpty();
    }

    public void push(T t) {
        list.addLast(t);
    }

    public T pop() {
        return list.removeLast();
    }
}

结果:

3. 队列(queue)

   在队列上的insert操作被称为enqueue(入队),而delete被称为dequeue(出对)。在现实生活中,我们在购物结账时所排的队就像队列数据结构一样(先到的人排在对列的前面,先结账,先离开)。

    如下图,描述的是队列Q在进行enqueue和dequeue操作的过程。队列有head(对头)和tail(队尾)。队列中的元素存放在Q.head与Q.tail-1之间。队列是首尾相连(环绕)的。初始时,Q.head = Q.tail = 1。当Q.head = Q.tail时,队列是空的,如果试图从空队列中删除一个元素,则发生队列下溢;当Q.head = Q.tail+1时,队列是满的,如果试图在满队列插入一个元素,则发生队列上溢。

    下面给出队列实现的伪代码(省略了溢出检查)

    下面我们来看一个应用队列的例子。(例子借鉴自:http://www.cnblogs.com/shenliang123/archive/2013/02/16/2913552.html

    在密码学中,恺撒密码是一种最简单且最广为人知的加密技术。据传是古罗马恺撒大帝用来保护重要军情的加密系统(不太可信)。它的具体做法是,将待加密的英文字符串的每个字母以字母表的顺序移动一定的位数来实现加密。这是很容易被破解的,因为移动的方案只有25种(假设每个字母移动相同的位数)。

    现在我们来考虑一个类似但稍微靠谱点加密方式(其实通过对字母出现频率的统计还是很容易破译)。我们先事约定一组密钥(用队列来存放),然后把每个字符按字母表顺序向右移动密钥中对应位置上的数。下面给出Java实现代码:

public class Main11_2 {

    public static final Integer[] SECRET_KEY = new Integer[] { -1, 3, 2, 5, 9, 0 };

    public static void main(String[] args) {
        String originaltext = "good good study,day day up!";
        System.out.println("原文是:" + originaltext);
        // 加密
        String ciphertext = encrypt(originaltext);
        System.out.println("密文是:" + ciphertext);
        // 解密
        String plaintext = decrypt(ciphertext);
        System.out.println("明文是:" + plaintext);
    }

    /**
     * 加密
     *
     * @param plaintext
     *            明文
     * @return 密文
     */
    private static String encrypt(String plaintext) {
        String ciphertext = "";
        if (plaintext == null || plaintext.isEmpty()) {
            return ciphertext;
        }
        Queue<Integer> queue = new Queue<>(SECRET_KEY);
        for (int i = 0; i < plaintext.length(); i++) {
            char c = plaintext.charAt(i);
            int key = queue.dequeue();
            ciphertext += (char) (c + key);
            queue.enqueue(key);
        }
        return ciphertext;
    }

    /**
     * 解密
     *
     * @param ciphertext
     *            密文
     * @return 明文
     */
    private static String decrypt(String ciphertext) {
        String plaintext = "";
        if (ciphertext == null || ciphertext.isEmpty()) {
            return plaintext;
        }
        Queue<Integer> queue = new Queue<>(SECRET_KEY);
        for (int i = 0; i < ciphertext.length(); i++) {
            char c = ciphertext.charAt(i);
            int key = queue.dequeue();
            plaintext += (char) (c - key);
            queue.enqueue(key);
        }
        return plaintext;
    }
}

class Queue<E> {

    private LinkedList<E> list;

    public Queue() {
        this(null);
    }

    public Queue(E[] objs) {
        list = new LinkedList<>();
        if (objs != null) {
            for (E e : objs) {
                enqueue(e);
            }
        }
    }

    public boolean isEmpty() {
        return list.isEmpty();
    }

    public void enqueue(E e) {
        list.addLast(e);
    }

    public E dequeue() {
        return list.removeFirst();
    }
}

结果:

时间: 2024-10-21 06:50:29

基本数据结构(1)——算法导论(11)的相关文章

数据结构与算法导论之基本概念和术语介绍

为了与大家取得"共同的语言",下面对一些概念和术语赋予确定的含义. 1.数据(data):对客观事物的符号表示,在计算科学中指所有能输入到计算机中并被计算机程序处理的符号总称. 2.数据元素(data element):是数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理.一个数据元素可以由若干个数据项(data item)组成,数据项是数据不可分割的最小单位. 3.数据对象(data object):性质相同的数据元素的组合,是数据的一个子集. 总结而言,数据.数据对象.数

【原创】算法导论11章带星第4题试解

题目: 我们希望在一个[非常大]的数组上,通过利用直接寻址的方式来实现一个字典.开始时,该数组中可能包含一些无用信息,但要对整个数组进行初始化是不太实际的,因为该数组的规模太大.请给出在大数组上实现直接寻址字典的方案.每个存储对象占用O(1)空间:SEARCH.INSERT和DELETE操作的时间均为O(1):并且对数据结构初始化的时间为O(1). (提示:可以利用一个附加数组,处理方式类似于栈,其大小等于实际存储在字典中的关键字数目,以帮助确定大数组中某个给定的项是否有效) 解答: 这个提示非

python数据结构与算法(11)

队列队列(queue)是只允许在?端进?插?操作,?在另?端进?删除操作的 线性表.队列是?种先进先出的(First In First Out)的线性表,简称FIFO.允许插? 的?端为队尾,允许删除的?端为队头.队列不允许在中间部位进?操作! 假设队列是q=(a1,a2,--,an),那么a1就是队头元素,?an是队尾 元素.这样我们就可以删除时,总是从a1开始,?插?时,总是在队列最 后.这也?较符合我们通常?活中的习惯,排在第?个的优先出列,最后来 的当然排在队伍最后. 队列的实现同栈?样

算法导论11:优化后的数组实现的队列 2016.1.11

本来想假期再继续,结果发现写博客已经成了总结自己的一种习惯,所以还是继续写吧. 其实有一部分原因是今天英语考砸了..哈哈,不管那些了,看来以后学习方式得改变一下,不能太功利,从简单开始,一点一点做好. 今天是队列的数组优化.昨天的队列有一个致命弱点,就是能盛放的数据数量越来越少,至于原因可以自己思考也可以上网搜索.如果是链表实现的话因为是动态分配空间,就完全不会有这个问题. 优化就是用了取模运算,实现数组空间的循环运用. 下面是代码: #include<stdio.h> #define MAX

【python cookbook】【数据结构与算法】11.对切片命名

问题:如何清理掉到处都是硬编码的切片索引 解决方案:对切片命名 假设有一些代码用来从字符串的固定位置中取出具体的数据(比如从一个平面文件或类似的格式:平面文件flat file是一种包含没有相对关系结构的记录文件): ########0123456789012345678901234567890123456789012345678901234567890123456789 record='....................100.......513.25..........' cost=i

算法导论11.4开放寻址法(除法哈希函数开放寻址处理冲突)

/* * IA_11.4OpenAddressing.cpp * * Created on: Feb 12, 2015 * Author: sunyj */ #include <stdint.h> #include <string.h> #include <iostream> class Node { public: Node() { } Node(int64_t const k, int64_t const d) : key(k), data(d) { } int64

算法导论11.2散列表Hash tables链式法解决碰撞

/* * IA_11.2ChainedHash.cpp * * Created on: Feb 12, 2015 * Author: sunyj */ #include <stdint.h> #include <iostream> #include <string.h> // CHAINED-HASH-INSERT(T, x) // insert x at the head of list T[h(x.key)] // CHAINED-HASH-SEARCH(T, k)

算法导论 学习资源

学习的过程会遇到些问题,发现了一些比较好的资源,每章都会看下别人写的总结,自己太懒了,先记录下别人写的吧,呵呵. 1  Tanky Woo的,每次差不多都看他的 <算法导论>学习总结 - 1.前言 <算法导论>学习总结 - 2.第一章 && 第二章 && 第三章 <算法导论>学习总结 - 3.第四章 && 第五章 <算法导论>学习总结 - 4.第六章(1) 堆排序 <算法导论>学习总结 - 5.第六

算法导论——lec 11 动态规划及应用

和分治法一样,动态规划也是通过组合子问题的解而解决整个问题的.分治法是指将问题划分为一个一个独立的子问题,递归地求解各个子问题然后合并子问题的解而得到原问题的解.与此不同,动态规划适用于子问题不是相互独立的情况,即各个子问题包含公共的子子问题.在这种情况下,如果用分治法会多做许多不必要的工作,重复求解相同的子子问题.而动态规划将每个子问题的解求解的结果放在一张表中,避免了重复求解. 一. 动态规划介绍 1. 动态规划方法介绍: 动态规划主要应用于最优化问题, 而这些问题通常有很多可行解,而我们希