美文网首页
PAT数据结构基础-线性结构练习

PAT数据结构基础-线性结构练习

作者: 我是靠谱好青年 | 来源:发表于2015-05-08 16:10 被阅读847次

    这个月计划把《数据结构与算法分析-C语言描述》重温一遍,恶补一下自己数据结构与算法方面的短板。这几天断断续续把最基本的线性结构一章看完,主要是讲了表、栈和队列三种数据结构的原理、实现以及应用。

    主要操作有Insert、Delete和Find几种。数组实现能够在O(1)时间进行查找第K项操作,但是Insert和Delete操作则需要O(N)时间,因为其需要大量移动和复制数组元素;而链表实现则刚好相反,能够在O(1)时间内进行Insert和Delete的操作,但是其查找第K项的操作则需要O(N)时间。

    是一种LIFO(Last-In-First-Out)的数据结构,主要操作有Push和Pop两种,Push操作把新元素压入栈顶,而Pop操作则把栈顶元素移出。栈的实现方法也有数组和链表两种,数组实现的主要缺点在于要先定义一个固定大小的数组,当保存的元素数量大于数组大小后,数据需要重新分配更大的空间并把原有元素复制到新空间中。栈结构主要的应用包括前缀、中缀、后缀表达式的计算与转换,以及程序运行时的函数调用也用到了栈结构。

    队列是一种FIFO(First-In-First-Out)的数据结构,主要操作有Enqueue(入队)和Dequeue(出队)两种,和栈一样,队列也可通过数组或链表实现。队列结构的主要应用有很多,如计算机系统内的消息队列等。

    1. Reversing Linked List

    Given a constant K and a singly linked list L, you are supposed to reverse the links of every K elements on L. For example, given L being 1→2→3→4→5→6, if K = 3, then you must output 3→2→1→6→5→4; if K = 4, you must output 4→3→2→1→5→6.
    Input Specification:
    Each input file contains one test case. For each case, the first line contains the address of the first node, a positive N (<= 105) which is the total number of nodes, and a positive K (<=N) which is the length of the sublist to be reversed. The address of a node is a 5-digit nonnegative integer, and NULL is represented by -1.
    Then N lines follow, each describes a node in the format:
    Address Data Next
    where Address is the position of the node, Data is an integer, and Next is the position of the next node.
    Output Specification:
    For each case, output the resulting ordered linked list. Each node occupies a line, and is printed in the same format as in the input.
    Sample Input:
    00100 6 4
    00000 4 99999
    00100 1 12309
    68237 6 -1
    33218 3 00000
    99999 5 68237
    12309 2 33218
    Sample Output:
    00000 4 33218
    33218 3 12309
    12309 2 00100
    00100 1 99999
    99999 5 68237
    68237 6 -1

    思路:既然题目已经给出了元素的个数,所以我使用了数组实现,毕竟数组的元素交换比链表的实现更简单。思路非常简单,读入数据,构造链表,然后交换链表元素并更新每个元素的Next指针。一开始中了一个坑就是,并不是所有元素都是链表中的元素,有些输入元素是没用的。

    代码如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    // 定义的数组元素
    typedef struct Node {
        char s[6];
        int data;
        char next[6];
    } Node;
    
    // 交换数组中两个元素
    void swap (Node *Array, int index1, int index2) {
        Node temp;
        if (index1 == index2) {
            return ;
        }
        memcpy(&temp, Array + index1, sizeof(Node));
        memcpy(Array + index1, Array + index2, sizeof(Node));
        memcpy(Array + index2, &temp, sizeof(Node));
    }
    
    int  main () {
        int num;
        int changeNum;
        char baseAd[6];
        Node *Array = NULL;
        int i, j;
        int mid;
    
        scanf("%s %d %d", baseAd, &num, &changeNum);
        if (0 == strcmp("-1", baseAd)) {
            return 1;
        }
    
        Array = malloc (num * sizeof(Node));
        if (NULL == Array) {
            printf("Malloc Failed!\n");
            return 1;
        }
        // 读入所有元素
        for (i = 0; i < num; i++) {
            scanf("%s %d %s", Array[i].s, &(Array[i].data), Array[i].next);
        }
        // 找到链表的头结点并放置在数组的0位置
        for (i = 0; i < num; i++) {
            if (0 == strcmp(Array[i].s, baseAd)) {
                swap(Array, 0, i);
                break;
            }
        }
        // 从数组0位置的元素开始,根据元素的Next指针不断找到下一个位置应放置的元素,读到Next为-1则表示结束,并记录数组中有效元素的个数
        for (i = 0; i < num - 1; i++) {
            for (j = i + 1; j < num; j++) {
                if (0 == strcmp(Array[i].next, Array[j].s)) {
                    swap(Array, i + 1, j);
                    break;
                }
            }
            if (0 == strcmp(Array[i + 1].next, "-1")) {
                num = i + 2;
                break;
            }
        }
        // reverse数组元素
        mid = changeNum / 2;
        for (i = 0; i < num; i += changeNum) {
            if ((i + changeNum) > num) { //数组最后剩下的不足changeNum长的不需要reverse
                break;
            }
            for (j = 0; j < mid; j++) {
                swap(Array, i + j, i + changeNum - j - 1);
            }
        }
    
        for (i = 0; i < num; i++) {
            if (i != (num -1)) {  // 更新元素的Next指针
                strcpy(Array[i].next, Array[i + 1].s);
            } else {
                strcpy(Array[i].next, "-1");
            }
            printf("%s %d %s", Array[i].s, Array[i].data, Array[i].next);  // 打印元素
            if (i != (num - 1)) {
                printf("\n");
            }
        }
        free(Array);  // 最后不能忘了释放空间
        return 0;
    }
    

    2. 一元多项式求导

    设计函数求一元多项式的导数。(注:xn(n为整数)的一阶导数为n*xn-1。)
    输入格式:以指数递降方式输入多项式非零项系数和指数(绝对值均为不超过1000的整数)。数字间以空格分隔。
    输出格式:以与输入相同的格式输出导数多项式非零项的系数和指数。数字间以空格分隔,但结尾不能有多余空格。注意“零多项式”的指数和系数都是0,但是表示为“0 0”。

    输入样例:
    3 4 -5 2 6 1 -2 0
    输出样例:
    12 3 -10 1 6 0

    思路:这道题比较简单,实际上并不需要使用链表就可以输出,不过本着多写多练的心,还是用链表完整的写了一下。要注意的是,当导数为零的时候要输出“0 0”.

    代码如下:

    #include <stdio.h>
    #include <stdlib.h>
    
    typedef struct Node {
        int co;
        int power;
        struct Node *next;
    } Node;
    
    int main () {
        int co;
        int power;
        int flag = 0;
    
        Node *head, *newNode, *tempNode;
        head = malloc (sizeof (Node));
        if (NULL == head) {
            return 1;
        }
    
        head->co = 0;
        head->power = 0;
        head->next = NULL;
        tempNode = head;
        // scanf input
        while (EOF != (scanf("%d %d", &co, &power))) {
            newNode = malloc (sizeof (Node));
            if (NULL == newNode) {
                return 1;
            }
            newNode->co = co;
            newNode->power = power;
            newNode->next = NULL;
            tempNode->next = newNode;
            tempNode = newNode;
        }
        // cal result
        tempNode = head->next;
    
        if (NULL != tempNode) {
            tempNode->co = tempNode->co * tempNode->power;
            tempNode->power--;
            if (0 != tempNode->co) {
                flag = 1; // 导数不为0
                printf("%d %d", tempNode->co, tempNode->power);     
            }
            tempNode = tempNode->next;
            while (NULL != tempNode) {
                tempNode->co = tempNode->co * tempNode->power;
                tempNode->power--;
                if (0 != tempNode->co) {
                    printf(" %d %d", tempNode->co, tempNode->power);        
                }
                tempNode = tempNode->next;
            }
        }
        // 如果导数为0,则输出0 0
        if (!flag) {
            printf("0 0");
        } 
        // free memory
        tempNode = head;
        while (NULL != tempNode) {
            head = tempNode->next;
            free(tempNode);
            tempNode = head;
        }
        return 0;
    }
    

    3. 求前缀表达式的值

    算术表达式有前缀表示法、中缀表示法和后缀表示法等形式。前缀表达式指二元运算符位于两个运算数之前,例如2+3*(7-4)+8/4的前缀表达式是:+ + 2 * 3 - 7 4 / 8 4。请设计程序计算前缀表达式的结果值。

    输入格式说明:
    输入在一行内给出不超过30个字符的前缀表达式,只包含+、-、*、\以及运算数,不同对象(运算数、运算符号)之间以空格分隔。

    输出格式说明:
    输出前缀表达式的运算结果,精确到小数点后1位,或错误信息“ERROR”。

    样例输入与输出

    1. + + 2 * 3 - 7 4 / 8 4 : 13.0
    2. / -25 + * - 2 3 4 / 8 4 : 12.5
    3. / 5 + * - 2 3 4 / 8 2 : ERROR
    4. +10.23 : 10.2

    思路:关于前缀、中缀和后缀表达式等基本上是要用到栈结构的。观察前缀表达式,一个运算符之后如果是两个操作数的话那么可以进行计算,因此可以想到,当从左到右读入表达式时,当读入运算符时压入栈中,当读入操作数时,判断栈顶是否也是操作数,如果是操作数,则pop出该操作数作为operand1,读入的操作数作为operand2,继续pop出栈顶的操作符(pop出操作数之后的栈顶元素肯定是操作符),然后进行运算。得到结果后,继续判断栈顶是否还是操作数,重复同样运算,直到不是操作数后,将结果压入栈中,继续读入下一个数。算法思路倒是问题不到,但是在字符串的操作方面遇到了些问题,然后又没有C/C++的调试环境,最后还是用java写了,java对字符串的操作还是比较简单的,特别是处理字符串和数字之间的转换。

    代码如下:

    import java.text.DecimalFormat;
    import java.util.LinkedList;
    import java.util.Scanner;
    
    
    public class Main {
        public static void main (String[] args) {
            DecimalFormat decimalFormat = new DecimalFormat(".#");
            Scanner in = new Scanner(System.in);
            String line = in.nextLine(); // 读入一行输入
            String[] array = line.split(" "); // 将输入字符串split
            LinkedList<String> stack = new LinkedList<String>();
            String temp;
            String op;
            
            
            double operand1, operand2;
            
            for (int i = 0; i < array.length; i++) {
                temp = array[i];
                if (!isOperator(temp)) {
                    operand2 = Double.parseDouble(temp);
                    while (!stack.isEmpty() && !isOperator(stack.peek())) {  //只要栈顶是操作数,则进行运算
                        operand1 = Double.parseDouble(stack.pop());
                        op = stack.pop();
                        if (op.equals("/") && operand2 == 0) {  // 除数为0,输出ERROR
                            System.out.print("ERROR");
                            System.exit(0);
                        } else {
                            operand2 = calculate(operand1, operand2, op);
                        }
                    }
                    stack.push(String.valueOf(operand2)); // 当栈顶不再操作数,将当前结果压入栈中
                } else {
                    stack.push(temp);  // 如果输入是操作符,则直接压栈
                }
            }
            operand1 = Double.parseDouble(stack.pop());
            System.out.print(decimalFormat.format(operand1));
        }
        
        // 计算结果
        public static double calculate (double opr1, double opr2, String op) {
            double result = 0;
            char c = op.charAt(0);
            switch (c) {
            case '+': result = opr1 + opr2; break;
            case '-': result = opr1 - opr2; break;
            case '*': result = opr1 * opr2; break;
            case '/': result = opr1 / opr2; break;
            }
            return result;
        }
        
        // 判断是不是操作符
        public static boolean isOperator (String s) {
            if (s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")) {
                return true;
            }
            return false;
        }
    }
    

    4. Pop Sequence

    Given a stack which can keep M numbers at most. Push N numbers in the order of 1, 2, 3, ..., N and pop randomly. You are supposed to tell if a given sequence of numbers is a possible pop sequence of the stack. For example, if M is 5 and N is 7, we can obtain 1, 2, 3, 4, 5, 6, 7 from the stack, but not 3, 2, 1, 7, 5, 6, 4.

    Input Specification:
    Each input file contains one test case. For each case, the first line contains 3 numbers (all no more than 1000): M (the maximum capacity of the stack), N (the length of push sequence), and K (the number of pop sequences to be checked). Then K lines follow, each contains a pop sequence of N numbers. All the numbers in a line are separated by a space.

    Output Specification:
    For each pop sequence, print in one line "YES" if it is indeed a possible pop sequence of the stack, or "NO" if not.

    Sample Input:
    5 7 5
    1 2 3 4 5 6 7
    3 2 1 7 5 6 4
    7 6 5 4 3 2 1
    5 6 4 3 7 2 1
    1 7 6 5 4 3 2
    Sample Output:
    YES
    NO
    NO
    YES
    NO

    思路:模拟入栈出栈操作。数是由1开始从小到大入栈的,如果出栈序列中出现了某数字,那么比该数字小的数字肯定已经先压入栈中。因此,算法思路是:记录最上一次压栈的数字lastInt,每当读入一个出栈序列中的数字时,如果该数字等于栈顶元素,则pop栈顶元素并继续读入下一个出栈序列上的数字;如果该数字不等于栈顶元素,则从lastInt+1开始,不断压入数字,直到压入当前出栈数字,过程中如果栈元素超过最大容量,则表示该出栈序列不可能,否则继续压栈并pop当前出栈数字。当所有出栈数字读入完,判断栈是否为空,如果不为空则表示该出栈序列不可能。

    代码如下:

    #include <iostream>
    #include <stack>
    
    using namespace std;
    
    // 处理pop序列的函数
    bool isPopSeq (int array[], int n, int m) {
        stack<int> stack;
        int curInt;
        int lastOut = 0; // 记录上一次入栈的数字
        for (int i = 0; i < n; i++) {
            curInt = array[i];
            if (!stack.empty() && stack.top() == curInt) { // 如果当前数字等于栈顶数字,则栈顶出栈
                stack.pop();
                continue;
            }
            if (stack.empty() || stack.top() < curInt) {  // 如果当前数字小于栈顶数字,则从上一次压栈的数字开始不断压栈
                for (int j = lastOut + 1; j <= curInt; j++) {
                    stack.push(j);
                    if (stack.size() > m) {  // 如果栈空间不够,表示该序列不可能
                        return false;
                    }
                }
                lastOut = stack.top(); // 更新lastOut为当前栈顶元素
                stack.pop(); // 将当前栈顶元素pop
            }
        }
        if (!stack.empty()) {
            return false;
        }
        return true;
    }
    
    int main () {
        int m, n, k;
        cin >> m >> n >> k;
    
        int *pop = new int[n];
    
        for (int i = 0; i < k; i++) {
            for (int j = 0; j < n; j++) {
                cin >> pop[j];
            }
            if (isPopSeq(pop, n, m)) {
                cout << "YES" << endl;
            } else {
                cout << "NO" << endl;
            }
        }
        return 0;
    }
    

    相关文章

      网友评论

          本文标题:PAT数据结构基础-线性结构练习

          本文链接:https://www.haomeiwen.com/subject/oevwfttx.html