數據結構與算法: 棧排序分析

棧排序

1 ) 棧結構

遵循LIFO原則,first in last out

2 ) 排序

這裏通過插入排序來分析

通過cpp方式實現

// 這裏使用萬能頭
#include <bits/stdc++.h>
using namespace std;

stack<int> sorting(stack<int>);

int main() {
    int n;
    cin >> n; // 輸入總個數
    stack<int> myStack;
    for (int i = 0; i < n; ++i) {
        int tmp;
        cin >> tmp; // 逐個輸入
        myStack.push(tmp);
    }

    stack<int> result = sorting(myStack);
    vector<int> answer;
    while (!result.empty()) {
        answer.push_back(result.top());
        result.pop();
    }
    // 這裏可行,auto在c++11中使用
    for (auto it = answer.rbegin(); it != answer.rend(); ++it){
        cout << *it << endl;
    }
    return 0;
}

// myStack:輸入棧,棧中的所有元素即是待排序的元素
// 返回值:輸出棧,即排序後的序列,滿足從棧底至棧頂爲升序
stack<int> sorting(stack<int> myStack) {
    stack<int> result; // result存放返回值,即輸出棧
    // 輸入的隨機棧是空的,那麼直接返回空的輸出
    if(myStack.empty())
        return result;

    int tmp = myStack.top(); // 記錄下一個要插入result棧中的數,cpp語言pop後不會返回,通過pop獲取棧頂元素
    myStack.pop(); // 彈出棧頂元素
    // 注意這裏循環條件的邊界判斷, 輸入棧不能是空的 或 
    // 在輸入棧空的前提下,最後一個使輸入棧空的彈出元素tmp 小於 輸出棧的棧頂元素, 最後一次滿足循環條件
    while(!myStack.empty() || (!result.empty() && tmp < result.top())) {
        // 棧頂元素比臨時值小,正常push 注意邊界條件: 棧空了可以直接接push
        if(result.empty() || result.top() <= tmp) {
            result.push(tmp);
            tmp = myStack.top();
            myStack.pop(); // 更新tmp
        } else {
            myStack.push(result.top());
            result.pop();
        }
    }
    // 輸入棧myStack迭代空了,且其最後一個元素tmp大於輸出棧result的棧頂元素
    result.push(tmp);
    return result;
}

python版本

#!/usr/bin/env pypy3
# -*- coding: UTF-8 -*-

# 用於測試的數據
def sorting(stack):
    '''
        此方法用於棧排序的方法
    '''
    # 邊界檢測
    if len(stack) != num:
        print('wrong input, please check!')
        return None;
    result = []
    # 檢測邊界: 初始值爲空, 返回自身
    if len(stack) == 0:
        return stack
    # 棧頂出棧並返回棧頂元素 記錄下一個要插入result中的數
    aTop = stack.pop()
    # 開始循環
    while (len(stack) != 0) or ((len(result) != 0 and result[len(result) - 1] > aTop)):
        if (len(result) == 0 or (result[len(result) - 1] <= aTop)):
            result.append(aTop) # 直接進棧
            aTop = stack.pop() # 準備下一個元素
        else:
            stack.append(result.pop())
    result.append(aTop)
    return result

if __name__ == '__main__':
    # input 輸入
    num = int(input()) # Enter an integer length: 
    stackStr = input() # Enter a list of number split by a space: 
    stack = stackStr.split(" ") # 解析成列表
    stack = [int(stack[i]) for i in range(len(stack))]  # for循環,把每個字符轉成int值
    # 進行排序
    res = sorting(stack)
    if res is None:
        print('wrong answer!')
    else:
        # print(res)
        for item in res:
            print(item)

輸入樣例爲:

4
4 3 2 1

輸出樣例爲:

1
2
3
4

3 ) 思路

這裏主要思路是:準備2個棧,myStack是初始狀態隨機輸入的棧無序,result是最終的輸出棧,有序。將myStack棧頂元素與result中的所有元素做比較,找到合適的位置後將result棧中該位置後面的元素全部依次存到myStack棧頂,之後再將這些移動的元素請回result棧中,達到一次插入排序,如此循環操作,核心思想是:myStack中取出的棧頂與result棧中的棧頂元素做比較。

4 ) 說明

  • sorting函數是主要的排序程序,注意各種邊界條件的判斷, 參考註釋內容
  • 問題在哪裏: 來回比較效率不高,如果空間緊張且只能用棧來做,也許這是最好的辦法
  • 這裏涉及一個就地(inplace)的問題, 要求算法所使用的空間只有一個單位的常數空間
  • 拿cpp版本中result.empty() || result.top() <= tmp來說 這裏 <= 換成 < 是不行的
    • 如果存在重複元素,它會進進出出,會出現死循環
    • 輸入序列中有兩個以上重複元素就會使排序出現穩定性問題,需要注意邊界問題
    • 等號成立的時候直接push即可
  • 關於逆序對的問題
    • 例如cpp版本中if(result.empty() || result.top() <= tmp) { ... } 這裏什麼時候result是空的,會有幾次
    • 初始狀態下result一定是空的,也就是壓入第一個元素的時候
    • 其他情況下,result被掏空的的情況在tmp比result最小的元素還要小
    • 也就是說,如果原輸入棧myStack是有序的,如:…4321這樣 ,最多時result被掏空的次數爲輸入棧的個數
    • 如果原輸入棧是有序的,這樣:1234…,那麼result在最初始化的時候只有一次爲空
    • 那麼這個else分支爲執行幾次呢? 這個次數爲原輸入棧的逆序對數目
    • 對於一個序列如:2967,它有兩個逆序對,首先逆序對是兩個位置(i,j)
    • 如果i < j 並且 ai>aja_i > a_j , 我們說(i,j)是一個逆序對
    • 直白的說,兩個元素,相對位置關係和相對大小關係是相反的,這就是一個逆序對
    • 上面2967有兩個逆序對,也就是9,6、9,7分別對應的位置,如果從0開始的話是(1,2),(1,3)
    • 在這裏輸出棧result是單調遞增的, 當result棧頂元素要被彈出的時候,一定是tmp小於這個棧頂元素,而tmp在之前輸入棧myStack中是棧頂,也就是棧的最右邊
    • 最右邊位置較大,而值較小,相比之下兩者則構成一個逆序對
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章