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

这个月计划把《数据结构与算法分析-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;
}
    原文作者:我是靠谱好青年
    原文地址: https://www.jianshu.com/p/44dbe6c02b59
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞