這個(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”。
樣例輸入與輸出
- + + 2 * 3 - 7 4 / 8 4 : 13.0
- / -25 + * - 2 3 4 / 8 4 : 12.5
- / 5 + * - 2 3 4 / 8 2 : ERROR
- +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;
}