棧的定義--Stack
棧只允許在末端進行插入和刪除的線性表。棧具有後進先出的特性(LIFO,Last In First Out)。
650) this.width=650;" src="/e/u261/themes/default/images/spacer.gif" style="background:url("/e/u261/lang/zh-cn/images/localimage.png") no-repeat center;border:1px solid #ddd;" alt="spacer.gif" />650) this.width=650;" src="http://s2.51cto.com/wyfs02/M02/7E/EC/wKioL1cNDoHDV1cAAAANevlioW4359.png" title="ScreenClip.png" alt="wKioL1cNDoHDV1cAAAANevlioW4359.png" />
問題:
實現一個棧,要求實現Push(出棧)、Pop(入棧)、Min(返回最小值的操作)的時間複雜度爲O(1)
問題分析:
根據棧的特性要實現入棧和出棧是比較容易的,只需要藉助系統給的stack即可。但是最難的是要滿足Min(返回最小值的操作)
的時間複雜度爲O(1)
我們先來討論最一般的思路:
思路1
使用一個變量來保存最小的值_min,每次入棧時讓入棧的元素d和_min進行比較,如果d<_min則更新_min,否則不更新。
但是這存在一個很大的問題,如果最小值出棧了怎麼辦?
比如說有一個棧爲:8,10,6,2,9.
則_min的值爲2,9出棧的話那麼_min的值不受影響。但是如果2出棧的話,則_min的值就無法得到。
思路2
既然只用一個變量沒法解決這個問題,那我們就增加變量。_min 保存當前的最小值,_second 保存次小值。
但是在實現Pop的時候又出現了新的問題,如果_min和_second都出棧之後,_min和_second該如何更新?
假設有棧爲8,10,6,2,9
_min爲2,_second爲6.當2出棧之後_min更新爲_second,但是_second該如何更新?
思路3
既然需要不斷的更新最小值,那就把所有的最小值給保存起來。
解決方案一:
每次push()和pop()時將數據和最小值同時壓入。
650) this.width=650;" src="/e/u261/themes/default/images/spacer.gif" style="background:url("/e/u261/lang/zh-cn/images/localimage.png") no-repeat center;border:1px solid #ddd;" alt="spacer.gif" />650) this.width=650;" src="http://s5.51cto.com/wyfs02/M00/7E/EF/wKiom1cNDf7zQsb5AAARJIvSZBM835.png" title="Image.png" alt="wKiom1cNDf7zQsb5AAARJIvSZBM835.png" />
假設有一個棧 8 7 10 6 2 2 9
則存儲的結構爲:
650) this.width=650;" src="/e/u261/themes/default/images/spacer.gif" style="background:url("/e/u261/lang/zh-cn/images/localimage.png") no-repeat center;border:1px solid #ddd;" alt="spacer.gif" />
入棧時的操作:
1)如果棧爲空或者入棧的元素d比當前棧的最小元素_s.top()._min小,則_min=d;
2)如果入棧元素d比當前棧的元素大於或者等於則直接將當前棧的最小值_s.top()._min賦給_min。
出棧操作:直接出棧
返回Min則 :當棧不爲空時,返回當前棧頂元素的_min即_s.top()._min。
實現代碼:
//節點結構
template<class T>
struct Node
{
public:
T _data;//數據域
T _min;//當前最小值
};
//棧的實現
template<class T>
class Stack
{
public:
void Push(const T& d)
{
Node<T> Data;
Data._data = d;//不能將d直接入棧,因爲棧爲一個結構體棧
if (_s.empty() || d < _s.top()._min)//如果棧爲空或者入棧元素小於當前棧的最小值
{
Data._min = d;
}
else//入棧元素大於等於當前棧的最小值
{
Data._min = _s.top()._min;
}
_s.push(Data);//入棧
}
void Pop()
{
_s.pop();
}
T& Min()
{
if (_s.empty())
{
throw exception("棧爲空");
}
return _s.top()._min;
}
private:
stack<Node<T>> _s;
};
總結:代碼實現起來比較容易簡潔,好理解可讀性比較強,但是對於重複比較多的棧的元素的存取則比較浪費空間,
因爲每次入棧和出棧時,都相當於入棧兩次。
例如:8、6、6、6、9、5、5、5、5、10、2、2、11、2、2
解決方案二:
使用兩個棧來實現。s1實現棧的push和pop,s2棧用於存儲在push和pop過程中的最小值
比如有一組數要入棧: 8,10,6,2,9
S1: 8,10,6,2,9
S2: 8 6 2
每次入棧時,都會和S2.top 比較,如果小於S2.top 則會同時入S1和S2棧。
即到棧頂9時,最小值是2, 如果S1棧9出,和S2.top比較,如果大於S2.top,則S2不做任何操作,如果等於S2.top, 則S2.pop();
比如S1的2出棧時,2 == S2.top(), 然後S2.pop();這樣就節省了很多空間。
但是上述方法還有問題:
比如數據
S1:8 7 10 6 2 2 2 9 這時
S2:8 7 6 2
當S1中2出棧時,S2中的2也會出棧,這樣從S2中讀取到的最小值就是6,但是明顯錯了。
解決方案:
既然棧中可能存在多個重複的最小值_min,解決方法有兩種:
方法一:S2中每個元素添加一個計數器。這樣當連續3次2出棧,S2中的2纔出棧。
方法二:S2中入棧的時候將<=S2.top的元素統統入棧,即S2爲8,7,6,2,2,2.
實現代碼(計數器):
template<typename T>
struct DataCount
{
public:
DataCount(const T & d)
:_data(d)
,_count(1)
{}
public:
T _data;
int _count;
};
template<typename T>
class Stack
{
//拷貝構造、賦值、均使用默認的即可,因爲拷貝構造和賦值的時候調用的是stack的,不存在淺拷貝
public:
void Push(const T &d)
{
_s.push(d);//d入數據棧
DataCount<T> tmp(d);//由於_min爲DataCount的棧,所以d不能直接入棧
if (_min.empty()|| d < _min.top()._data)//如果存放最小的棧爲空或者小於
{
_min.push(tmp);
}
else if (d == _min.top()._data)//入棧的元素等於_min棧的棧頂元素,計數器_count+1;
{
_min.top()._count++;
}
}
void Pop()
{
if (_s.empty())
{
return;
//throw exception("棧爲空");
}
//如果數據棧出棧的元素等於_min棧的棧頂元素
if (_s.top() == _min.top()._data)
{
if (_min.top()._count==1)//數據棧中最小的元素只有一個
{
_min.pop();
}
else//數據棧中存在多個相等的最小元素
{
_min.top()._count--;
}
}
//如果數據棧出棧的元素大於_min棧的棧頂元素
//由於_min棧中存放的都是較小的元素,所以不可能比數據棧要出棧的元素更大
_s.pop();
}
T& Min()
{
if (_min.empty())
{
//return -1;
throw exception("棧爲空");
}
return _min.top()._data;
}
private:
stack<T> _s; //存儲基本數據的棧
stack<DataCount<T>> _min; //存儲最小值的棧
};
實現代碼(重複元素進棧)
template<typename T>
class Stack
{
public:
void Push(const T &d)
{
_s.push(d);//d入數據棧
if (_min.empty()|| d <= _min.top())//如果存放最小的棧爲空或者入棧的元素小於等於_min棧的棧頂元素
{
_min.push(d);
}
}
void Pop()
{
if (_s.empty())
{
return;
//throw exception("棧爲空");
}
//如果數據棧出棧的元素等於_min棧的棧頂元素
if (_s.top() == _min.top())
{
_min.pop();
}
//如果數據棧出棧的元素大於_min棧的棧頂元素
//由於_min棧中存放的都是較小的元素,所以不可能比數據棧要出棧的元素更大
_s.pop();
}
T& Min()
{
if (_min.empty())
{
//return -1;
throw exception("棧爲空");
}
return _min.top();
}
private:
stack<T> _s; //存儲基本數據的棧
stack<T> _min; //存儲最小值的棧
};
比較:
方法一:
優點:適用於大量的重複的較小值的存儲
缺點:在一般場景下空間浪費比較大,因爲S2中每個元素添加一個計數器。
例如:8、6、6、6、9、5、5、5、5、10、2、2、11、2、2
使用計數器比較節省空間。
方法二:
優點:在一般場下比較節省空間。
缺點:對於大量的重複的較小值得存儲比較浪費
例如:8、6、5、12、9、2、11、7
測試代碼:
void test()
{
Stack<int> s1;
s1.Push(10);
s1.Push(11);
s1.Push(6);
s1.Push(2);
s1.Push(2);
s1.Push(2);
s1.Push(12);
Stack<int> s3(s1);
Stack<int> s2;
s2 = s1;
try
{
cout << s1.Min() << endl;
s1.Pop();
cout << s1.Min() << endl;
s1.Pop();
cout << s1.Min() << endl;
s1.Pop();
cout << s1.Min() << endl;
s1.Pop();
cout << s1.Min() << endl;
s1.Pop();
cout << s1.Min() << endl;
s1.Pop();
cout << s1.Min() << endl;
s1.Pop();
cout << s1.Min() << endl;
s1.Pop();
cout << s1.Min() << endl;
s1.Pop();
cout << s1.Min() << endl;
}
catch (...)
{
cout << "棧爲空" << endl;
}
try
{
cout << s2.Min() << endl;
s2.Pop();
cout << s2.Min() << endl;
s2.Pop();
cout << s2.Min() << endl;
s2.Pop();
cout << s2.Min() << endl;
s2.Pop();
cout << s2.Min() << endl;
s2.Pop();
cout << s2.Min() << endl;
s2.Pop();
cout << s2.Min() << endl;
s2.Pop();
cout << s2.Min() << endl;
}
catch (...)
{
cout << "棧爲空" << endl;
}
}
int main()
{
test();
getchar();
return 0;
}