數據結構——使用棧判定迴文數

1.迴文數定義與題目解讀

迴文數是指正讀反讀都能讀通的句子。比如“我爲人人,人人爲我”、“1234321”、“abcba”是迴文數,但“一杯茶一包煙,一行代碼寫一天”,“123456”,“abcdef”就不是迴文數。
那麼通過以上的幾個例子可以發現以下幾點:

  1. 迴文數是要求這串文字的左側與右側相同,即迴文數左右對稱,但是若字符個數爲奇數的情況下,那麼中間的字符並不會對整個判斷其影響作用,所以由此可以推測出迴文數需要考慮整個文字的奇偶性。
  2. 由於是比較第i個字符與第n-i-1個字符(這裏是以數組遊標來計算的i,即i從0開始),那麼用棧解決無疑是最合適的方式。

2.棧的構建

使用棧那麼一共需要棧的init(), push(), pop()方法,筆者在這使用的是鏈式棧來解決問題,讀者有興趣也可以考慮使用順序棧實現。
考慮到可能會有數據結構的初學者,所以這裏簡單講解下鏈棧的工作模式:

  1. 棧是只允許在一端進行插入或刪除的線性表,棧的一個明顯的操作特性是先進後出(first in last out, FILO)。
  2. 鏈棧的push()方法就是在棧的頂部對該單鏈表進行前插操作,如圖所示:
    鏈棧的入棧操作
  3. 鏈棧的pop()方法就是刪除top所指向的結點,如圖所示:
    鏈棧的出棧操作
    相應鏈棧的構建代碼如下所示:
typedef struct SNode {
    char data;  // 數據域
    struct SNode *next;  // 指針域
}SNode, *SLink;

typedef struct LinkStack {
    SLink top;  // 棧頂指針
    int count;  // 棧中結點個數
}LinkStack;

/**
 * 初始化鏈式棧
 * param: S(需要操作的鏈棧)
 */ 
void initStack(LinkStack *&S) {
    S = (LinkStack *)malloc(sizeof(LinkStack));
    S->top = (SNode *)malloc(sizeof(SNode));
    
    S->top->next = NULL;
    S->top = NULL;
    S->count = 0;
}

/**
 * 向棧中加入數據
 * param: *S(需要操作的棧), elem(向棧中添加的元素)
 * return: true(操作成功)
 *         false(操作失敗)
 */ 
bool push(LinkStack *S, char elem) {
    SLink node = (SLink)malloc(sizeof(SNode));  // 創建新結點
    node->data = elem;
    node->next = S->top;
    S->top = node;
    S->count++;
    return true;
}

/**
 * 彈出棧頂元素
 * param: *S(需要操作的棧), &elem(彈出的第一個元素)
 * return: true(操作成功)
 *         false(棧爲空)
 */ 
bool pop(LinkStack *S, char &elem) {
    
    //空棧
    if(S->top == NULL) {
        return false;
    }

    elem = S->top->data;
    SNode *node = S->top;  // 工作指針
    S->top = S->top->next;
    free(node);
    S->count--;
    return true;
}

3.判斷迴文數方法

3.1 數組的輸入

之前在第一部分的時候提到了,迴文數判斷的核心在於判斷該數組的奇偶性,那麼對整個數組的長度的把握至關重要。由於C/C++不像python一樣有內置的len()函數,所以在計算數組長度的時候有些許麻煩。筆者在編碼過程中是手動鍵入數組每個數的,所以通過判斷輸入的次數來記錄長度。若讀者是採用直接ElemType arr[] = {}這種方式創建數組,則可以採用int len = sizeof(arr) / sizeof(arr[0]);這種方式計算。

/**
 * 鍵入數組, 鍵入*或者一共鍵入MaxSize個字符停止
 * param: &arr[](需要操作的數組)
 * return: length(數組長度)
 */ 
int enter_array(char arr[]) {
    char c;
    int length = 0;
    for (int i = 0; i < MaxSize; i++) {
        cin>>c;
        if (c == '*') {
            break;
        }
        arr[i] = c;
        length++;
    }
    return length;
}

3.2 迴文數方法

前面已經完成了相應的鋪墊工作,現在開始進入正題。
首先,最開始提到過,迴文數是判斷一個數組的左右是否對稱,那麼我們只需要考慮將數組前半段壓入棧中,即從int i = 0循環到i < length / 2。
有的讀者可能會有疑問,在C/C++中整型相除若產生浮點數,那麼會省略結果的小數點之後的值,即向下取整,爲什麼這裏還使用length / 2?因爲之前也提到過,一串奇數個字符是否爲迴文數,與其正中間的字符毫無關係,所以這裏可以大膽的使用i < length / 2。

// 將數組前半段入棧, 若length爲奇數, 則向下取整
for (int i = 0; i < length / 2; i++) {
    push(S, arr[i]);
}

接着,就是我們要考慮數組的奇偶性的時候了,因爲我們需要遍歷整個數組的後半部分的數據,那麼開始的起始位置就至關重要。
當字符數爲奇數個時,起始位置爲length / 2 + 1,當字符數爲偶數個時,起始位置爲length / 2
至於爲何爲這兩個值,筆者畫了個圖進行說明,紅色表明的部分爲數組應該開始遍歷的起始位置。
數組起始位置講解

// 判斷數組奇偶性
int start;  // 用於記錄數組中的字符與棧中字符比較的起始位置
    
// 若數組爲偶數
if (length % 2 == 0) {
        start = length / 2;
}
// 若數組爲奇數
else {
    start = length / 2 + 1;
}

最後就只剩下判斷數組遍歷的字符與出棧的字符是否相等,若不相等,則不是迴文數,若整個程序活到了遍歷結束,那麼就是迴文數。

for (int i = start; i < length; i++) {
    char c;
    pop(S, c);
        
    // 若出棧的字符與arr[i]不等
    if (c != arr[i]) {
       return false;
    }
}
    
return true;

4.算法分析

  1. 時間複雜度:整個算法雖然使用了兩個for循環,但是分別遍歷了數組的左右兩側,所以時間複雜度爲O(n)
  2. 空間複雜度:整個算法使用了棧這種數據結構與一個數組,而棧只存入了至多一半的數組長度,所以空間複雜度爲O(n + n / 2) = O(3n / 2)

5.整體代碼與結果截圖

#include <iostream>
using namespace std;

#define MaxSize 20

typedef struct SNode {
    char data;  // 數據域
    struct SNode *next;  // 指針域
}SNode, *SLink;

typedef struct LinkStack {
    SLink top;  // 棧頂指針
    int count;  // 棧中結點個數
}LinkStack;

/**
 * 初始化鏈式棧
 * param: S(需要操作的鏈棧)
 */ 
void initStack(LinkStack *&S) {
    S = (LinkStack *)malloc(sizeof(LinkStack));
    S->top = (SNode *)malloc(sizeof(SNode));
    
    S->top->next = NULL;
    S->top = NULL;
    S->count = 0;
}

/**
 * 向棧中加入數據
 * param: *S(需要操作的棧), elem(向棧中添加的元素)
 * return: true(操作成功)
 *         false(操作失敗)
 */ 
bool push(LinkStack *S, char elem) {
    SLink node = (SLink)malloc(sizeof(SNode));  // 創建新結點
    node->data = elem;
    node->next = S->top;
    S->top = node;
    S->count++;
    return true;
}

/**
 * 彈出棧頂元素
 * param: *S(需要操作的棧), &elem(彈出的第一個元素)
 * return: true(操作成功)
 *         false(棧爲空)
 */ 
bool pop(LinkStack *S, char &elem) {
    
    //空棧
    if(S->top == NULL) {
        return false;
    }

    elem = S->top->data;
    SNode *node = S->top;  // 工作指針
    S->top = S->top->next;
    free(node);
    S->count--;
    return true;
}

/**
 * 查看棧中元素
 * param: *S(需要操作的棧)
 */ 
void visit(LinkStack *S) {
    SLink node = S->top;
    while (node != NULL) {
        cout<<node->data<<"  ";
        node = node->next;
    }
    cout<<endl;
}

/**
 * 鍵入數組, 鍵入*或者一共鍵入MaxSize個字符停止
 * param: &arr[](需要操作的數組)
 * return: length(數組長度)
 */ 
int enter_array(char arr[]) {
    char c;
    int length = 0;
    for (int i = 0; i < MaxSize; i++) {
        cin>>c;
        if (c == '*') {
            break;
        }
        arr[i] = c;
        length++;
    }
    return length;
}

/**
 * 迴文數判斷
 * param: &S(需要操作的棧), arr[](需要判斷的數組), length(數組長度)
 * return: true(是迴文數), false(不是迴文數)
 */ 
bool is_palindrome_number(LinkStack *&S, char arr[], int length) {
    initStack(S);

    // 將數組前半段入棧, 若length爲奇數, 則向下取整
    for (int i = 0; i < length / 2; i++) {
        push(S, arr[i]);
    }

    // 判斷數組奇偶性
    int start;  // 用於記錄數組中的字符與棧中字符比較的起始位置
    
    // 若數組爲偶數
    if (length % 2 == 0) {
        start = length / 2;
    }
    // 若數組爲奇數
    else {
        start = length / 2 + 1;
    }

    // 迴文數判斷
    for (int i = start; i < length; i++) {
        char c;
        pop(S, c);
        
        // 若出棧的字符與arr[i]不等
        if (c != arr[i]) {
            return false;
        }
    }
    
    return true;
}

int main() {
    LinkStack *S;
    char arr[MaxSize];
    int length = enter_array(arr);
    
    if(is_palindrome_number(S, arr, length)) {
        cout<<"this array is a palindrome number"<<endl;
    } else {
        cout<<"this array is not a palindrome number"<<endl;
    }

    system("pause");
    return 0;
}

數組是迴文數截圖
素組不是迴文數截圖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章