面試題那些事(3)—棧

棧的定義--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" />

650) this.width=650;" src="http://s4.51cto.com/wyfs02/M00/7E/EC/wKioL1cNDsbBdrIzAAAbslUQTYM931.png" title="Image [1].png" alt="wKioL1cNDsbBdrIzAAAbslUQTYM931.png" />

入棧時的操作:

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;
}


發佈了53 篇原創文章 · 獲贊 2 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章