PAT數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)-線性結(jié)構(gòu)練習(xí)

這個(gè)月計(jì)劃把《數(shù)據(jù)結(jié)構(gòu)與算法分析-C語(yǔ)言描述》重溫一遍,惡補(bǔ)一下自己數(shù)據(jù)結(jié)構(gòu)與算法方面的短板。這幾天斷斷續(xù)續(xù)把最基本的線性結(jié)構(gòu)一章看完,主要是講了表、棧和隊(duì)列三種數(shù)據(jù)結(jié)構(gòu)的原理、實(shí)現(xiàn)以及應(yīng)用。

主要操作有Insert、Delete和Find幾種。數(shù)組實(shí)現(xiàn)能夠在O(1)時(shí)間進(jìn)行查找第K項(xiàng)操作,但是Insert和Delete操作則需要O(N)時(shí)間,因?yàn)槠湫枰罅恳苿?dòng)和復(fù)制數(shù)組元素;而鏈表實(shí)現(xiàn)則剛好相反,能夠在O(1)時(shí)間內(nèi)進(jìn)行Insert和Delete的操作,但是其查找第K項(xiàng)的操作則需要O(N)時(shí)間。

是一種LIFO(Last-In-First-Out)的數(shù)據(jù)結(jié)構(gòu),主要操作有Push和Pop兩種,Push操作把新元素壓入棧頂,而Pop操作則把棧頂元素移出。棧的實(shí)現(xiàn)方法也有數(shù)組和鏈表兩種,數(shù)組實(shí)現(xiàn)的主要缺點(diǎn)在于要先定義一個(gè)固定大小的數(shù)組,當(dāng)保存的元素?cái)?shù)量大于數(shù)組大小后,數(shù)據(jù)需要重新分配更大的空間并把原有元素復(fù)制到新空間中。棧結(jié)構(gòu)主要的應(yīng)用包括前綴、中綴、后綴表達(dá)式的計(jì)算與轉(zhuǎn)換,以及程序運(yùn)行時(shí)的函數(shù)調(diào)用也用到了棧結(jié)構(gòu)。

隊(duì)列是一種FIFO(First-In-First-Out)的數(shù)據(jù)結(jié)構(gòu),主要操作有Enqueue(入隊(duì))和Dequeue(出隊(duì))兩種,和棧一樣,隊(duì)列也可通過數(shù)組或鏈表實(shí)現(xiàn)。隊(duì)列結(jié)構(gòu)的主要應(yīng)用有很多,如計(jì)算機(jī)系統(tǒng)內(nèi)的消息隊(duì)列等。

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

思路:既然題目已經(jīng)給出了元素的個(gè)數(shù),所以我使用了數(shù)組實(shí)現(xiàn),畢竟數(shù)組的元素交換比鏈表的實(shí)現(xiàn)更簡(jiǎn)單。思路非常簡(jiǎn)單,讀入數(shù)據(jù),構(gòu)造鏈表,然后交換鏈表元素并更新每個(gè)元素的Next指針。一開始中了一個(gè)坑就是,并不是所有元素都是鏈表中的元素,有些輸入元素是沒用的。

代碼如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 定義的數(shù)組元素
typedef struct Node {
    char s[6];
    int data;
    char next[6];
} Node;

// 交換數(shù)組中兩個(gè)元素
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);
    }
    // 找到鏈表的頭結(jié)點(diǎn)并放置在數(shù)組的0位置
    for (i = 0; i < num; i++) {
        if (0 == strcmp(Array[i].s, baseAd)) {
            swap(Array, 0, i);
            break;
        }
    }
    // 從數(shù)組0位置的元素開始,根據(jù)元素的Next指針不斷找到下一個(gè)位置應(yīng)放置的元素,讀到Next為-1則表示結(jié)束,并記錄數(shù)組中有效元素的個(gè)數(shù)
    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數(shù)組元素
    mid = changeNum / 2;
    for (i = 0; i < num; i += changeNum) {
        if ((i + changeNum) > num) { //數(shù)組最后剩下的不足changeNum長(zhǎng)的不需要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. 一元多項(xiàng)式求導(dǎo)

設(shè)計(jì)函數(shù)求一元多項(xiàng)式的導(dǎo)數(shù)。(注:xn(n為整數(shù))的一階導(dǎo)數(shù)為n*xn-1。)
輸入格式:以指數(shù)遞降方式輸入多項(xiàng)式非零項(xiàng)系數(shù)和指數(shù)(絕對(duì)值均為不超過1000的整數(shù))。數(shù)字間以空格分隔。
輸出格式:以與輸入相同的格式輸出導(dǎo)數(shù)多項(xiàng)式非零項(xiàng)的系數(shù)和指數(shù)。數(shù)字間以空格分隔,但結(jié)尾不能有多余空格。注意“零多項(xiàng)式”的指數(shù)和系數(shù)都是0,但是表示為“0 0”。

輸入樣例:
3 4 -5 2 6 1 -2 0
輸出樣例:
12 3 -10 1 6 0

思路:這道題比較簡(jiǎn)單,實(shí)際上并不需要使用鏈表就可以輸出,不過本著多寫多練的心,還是用鏈表完整的寫了一下。要注意的是,當(dāng)導(dǎo)數(shù)為零的時(shí)候要輸出“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; // 導(dǎo)數(shù)不為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;
        }
    }
    // 如果導(dǎo)數(shù)為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. 求前綴表達(dá)式的值

算術(shù)表達(dá)式有前綴表示法、中綴表示法和后綴表示法等形式。前綴表達(dá)式指二元運(yùn)算符位于兩個(gè)運(yùn)算數(shù)之前,例如2+3*(7-4)+8/4的前綴表達(dá)式是:+ + 2 * 3 - 7 4 / 8 4。請(qǐng)?jiān)O(shè)計(jì)程序計(jì)算前綴表達(dá)式的結(jié)果值。

輸入格式說明:
輸入在一行內(nèi)給出不超過30個(gè)字符的前綴表達(dá)式,只包含+、-、*、\以及運(yùn)算數(shù),不同對(duì)象(運(yùn)算數(shù)、運(yùn)算符號(hào))之間以空格分隔。

輸出格式說明:
輸出前綴表達(dá)式的運(yùn)算結(jié)果,精確到小數(shù)點(diǎn)后1位,或錯(cuò)誤信息“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

思路:關(guān)于前綴、中綴和后綴表達(dá)式等基本上是要用到棧結(jié)構(gòu)的。觀察前綴表達(dá)式,一個(gè)運(yùn)算符之后如果是兩個(gè)操作數(shù)的話那么可以進(jìn)行計(jì)算,因此可以想到,當(dāng)從左到右讀入表達(dá)式時(shí),當(dāng)讀入運(yùn)算符時(shí)壓入棧中,當(dāng)讀入操作數(shù)時(shí),判斷棧頂是否也是操作數(shù),如果是操作數(shù),則pop出該操作數(shù)作為operand1,讀入的操作數(shù)作為operand2,繼續(xù)pop出棧頂?shù)牟僮鞣╬op出操作數(shù)之后的棧頂元素肯定是操作符),然后進(jìn)行運(yùn)算。得到結(jié)果后,繼續(xù)判斷棧頂是否還是操作數(shù),重復(fù)同樣運(yùn)算,直到不是操作數(shù)后,將結(jié)果壓入棧中,繼續(xù)讀入下一個(gè)數(shù)。算法思路倒是問題不到,但是在字符串的操作方面遇到了些問題,然后又沒有C/C++的調(diào)試環(huán)境,最后還是用java寫了,java對(duì)字符串的操作還是比較簡(jiǎn)單的,特別是處理字符串和數(shù)字之間的轉(zhuǎn)換。

代碼如下:

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())) {  //只要棧頂是操作數(shù),則進(jìn)行運(yùn)算
                    operand1 = Double.parseDouble(stack.pop());
                    op = stack.pop();
                    if (op.equals("/") && operand2 == 0) {  // 除數(shù)為0,輸出ERROR
                        System.out.print("ERROR");
                        System.exit(0);
                    } else {
                        operand2 = calculate(operand1, operand2, op);
                    }
                }
                stack.push(String.valueOf(operand2)); // 當(dāng)棧頂不再操作數(shù),將當(dāng)前結(jié)果壓入棧中
            } else {
                stack.push(temp);  // 如果輸入是操作符,則直接壓棧
            }
        }
        operand1 = Double.parseDouble(stack.pop());
        System.out.print(decimalFormat.format(operand1));
    }
    
    // 計(jì)算結(jié)果
    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

思路:模擬入棧出棧操作。數(shù)是由1開始從小到大入棧的,如果出棧序列中出現(xiàn)了某數(shù)字,那么比該數(shù)字小的數(shù)字肯定已經(jīng)先壓入棧中。因此,算法思路是:記錄最上一次壓棧的數(shù)字lastInt,每當(dāng)讀入一個(gè)出棧序列中的數(shù)字時(shí),如果該數(shù)字等于棧頂元素,則pop棧頂元素并繼續(xù)讀入下一個(gè)出棧序列上的數(shù)字;如果該數(shù)字不等于棧頂元素,則從lastInt+1開始,不斷壓入數(shù)字,直到壓入當(dāng)前出棧數(shù)字,過程中如果棧元素超過最大容量,則表示該出棧序列不可能,否則繼續(xù)壓棧并pop當(dāng)前出棧數(shù)字。當(dāng)所有出棧數(shù)字讀入完,判斷棧是否為空,如果不為空則表示該出棧序列不可能。

代碼如下:

#include <iostream>
#include <stack>

using namespace std;

// 處理pop序列的函數(shù)
bool isPopSeq (int array[], int n, int m) {
    stack<int> stack;
    int curInt;
    int lastOut = 0; // 記錄上一次入棧的數(shù)字
    for (int i = 0; i < n; i++) {
        curInt = array[i];
        if (!stack.empty() && stack.top() == curInt) { // 如果當(dāng)前數(shù)字等于棧頂數(shù)字,則棧頂出棧
            stack.pop();
            continue;
        }
        if (stack.empty() || stack.top() < curInt) {  // 如果當(dāng)前數(shù)字小于棧頂數(shù)字,則從上一次壓棧的數(shù)字開始不斷壓棧
            for (int j = lastOut + 1; j <= curInt; j++) {
                stack.push(j);
                if (stack.size() > m) {  // 如果棧空間不夠,表示該序列不可能
                    return false;
                }
            }
            lastOut = stack.top(); // 更新lastOut為當(dāng)前棧頂元素
            stack.pop(); // 將當(dāng)前棧頂元素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;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容