真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

【C++、數(shù)據(jù)結(jié)構(gòu)】AVL樹模擬實現(xiàn)-創(chuàng)新互聯(lián)

文章目錄
  • 📖 前言
  • 1. AVL樹的概念
      • 1.1 二叉搜索樹的缺點:
      • 1.2 AVL樹的引入:
      • 1.2 AVL樹的性質(zhì):
  • 2. AVL樹的模擬實現(xiàn)
      • 2.1 AVL樹結(jié)點的定義:
      • 2.2 AVL樹的插入:(重點)
        • 2.2.1 插入結(jié)點后平衡因子的變化
        • 2.2.2 插入結(jié)點后對其他結(jié)點衡因子的影響
        • 2.2.3 在不同位置插入情況分析處理
      • 2.3 AVL樹的旋轉(zhuǎn)操作:(重點)
        • 2.3.1 左單旋
        • 2.3.2 右單旋
        • 2.3.3 左右雙旋
        • 2.3.4 右左雙旋
  • 3. 驗證AVL樹
      • 3.1 嚴格驗證AVL樹:

創(chuàng)新互聯(lián)建站服務項目包括麥積網(wǎng)站建設、麥積網(wǎng)站制作、麥積網(wǎng)頁制作以及麥積網(wǎng)絡營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,麥積網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟效益。目前,我們服務的客戶以成都為中心已經(jīng)輻射到麥積省份的部分城市,未來相信會繼續(xù)擴大服務區(qū)域并繼續(xù)獲得客戶的支持與信任!📖 前言
  • 上一章節(jié)我們學習了二叉搜索樹,并且模擬實現(xiàn)了二叉搜索樹的實現(xiàn)。
  • 在學習完之后我們知道二叉搜索樹查找的時間復雜度是〇(N),這里并不是〇(logN) 具體的原因就是要搜索的二叉樹并不是非常的平衡。
  • 并不是所有要查找的二叉樹都是滿二叉樹或者是完全二叉樹,有可能是單邊樹的情況,平均下來的時間復雜度就是〇(N)。

前情回顧:二叉搜索樹 👉傳送門

本章我們將學習AVL樹,來解決上一章節(jié)二叉搜索樹的查找時二叉樹不平衡的問題,搬好小板凳準備開講啦~~~ 🙋 🙋 🙋 🙋 🙋


1. AVL樹的概念 1.1 二叉搜索樹的缺點:
  • 二叉搜索樹雖可以減少查找的效率,但如果數(shù)據(jù)有序或接近有序時二叉搜索樹將退化為單邊樹
  • 這時查找元素相當于在順序表中搜索元素,效率低下
  • 而且如果二叉樹查找是用遞歸實現(xiàn)的話,那么這種情況查找很有可能會導致棧溢出(爆棧)?。?!
1.2 AVL樹的引入:

兩位俄羅斯的數(shù)學家G.M.Adelson-Velskii和E.M.Landis(AVL樹就是以這兩位科學家的名字命名的)

在1962年發(fā)明了一種解決上述問題的方法:

當向二叉搜索樹中插入新結(jié)點后,如果能保證每個結(jié)點的左右子樹高度之差的絕對值不超過1(需要對樹中的結(jié)點進行調(diào)整),即可降低樹的高度,從而減少平均搜索長度。

1.2 AVL樹的性質(zhì):

首先AVL樹是一棵二叉搜索樹,一棵AVL樹或者是空樹,或者是具有以下性質(zhì)的二叉搜索樹:

  • 它的左右子樹都是AVL樹
  • 任何一顆子樹左右子樹高度之差(簡稱平衡因子)的絕對值不超過 1(-1 / 0 / 1)

在這里插入圖片描述

平衡不是相等,與滿二叉樹和完全二叉樹比較一下:(節(jié)點外數(shù)字代表平衡因子)

  • 滿二叉樹: 保證每個子樹高度差是0
  • 完全二叉樹: 最后一層缺一些結(jié)點
  • AVL樹: 最后兩層缺一些結(jié)點

AVL樹又叫高度平衡二叉搜索樹。

如果一棵二叉搜索樹是高度平衡的,它就是AVL樹。如果它有n個結(jié)點,其高度可保持在 〇(logN)搜索時間復雜度 〇(logN)。


2. AVL樹的模擬實現(xiàn) 2.1 AVL樹結(jié)點的定義:
  • 直接實現(xiàn)key_value的結(jié)構(gòu) – 三叉鏈的形式(帶父節(jié)點)

具體代碼如下:

templatestruct AVLTreeNode
{pair_kv;
	AVLTreeNode* _left;
	AVLTreeNode* _right;
	AVLTreeNode* _parent;
	int _bf;
	
	AVLTreeNode(const pair& kv)
		: _kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _bf(0)
	{}
};
  • 平衡因子 —— bakance factor
  • 右子樹 – 左子樹的高度差

AVL樹并沒有規(guī)定必須要設計平衡因子,只是一個實現(xiàn)的選擇,方便控制平衡。

2.2 AVL樹的插入:(重點)
  • 這里的插入思路和二叉搜索樹中插入的思路一致,找到合適的位置之后再鏈接

這里鏈接是比較容易的,但是鏈接之后對各個結(jié)點中的平衡因子的調(diào)整則是比較費勁的。

2.2.1 插入結(jié)點后平衡因子的變化

1. 首先我們來一段簡單的邏輯 —— 只考慮父子之間關(guān)系:

  • 當一個結(jié)點的左或者右鏈接了一個結(jié)點,該結(jié)點為鏈接結(jié)點的父節(jié)點
  • 當該父節(jié)點的右邊連接上孩子時,此時該父節(jié)點的右子樹比左子樹高了一層,平衡因子_bf + +
  • 當該父節(jié)點的左邊連接上孩子時,此時該父節(jié)點的左子樹比右子樹高了一層,平衡因子_bf – –

如圖所示:

在這里插入圖片描述

2.2.2 插入結(jié)點后對其他結(jié)點衡因子的影響

2. 插入一個結(jié)點對整個樹的影響:

  • 插入一個結(jié)點真正會影響的是其祖先的平衡因子的改變
  • 同時插入一個結(jié)點對兄弟結(jié)點的平衡因子沒有影響

如圖所示:

在這里插入圖片描述

2.2.3 在不同位置插入情況分析處理

3. 在鏈接新的結(jié)點的時候要滿足AVL樹的規(guī)則

(1)向上更新:

  • 更新新插入節(jié)點祖先的平衡因子
  • 沒有違反規(guī)則就結(jié)束了,違反規(guī)則,不平衡了就需要處理
  • 這里的處理是旋轉(zhuǎn)處理(接下來會重點介紹)
  • 在更新的過程中只要是發(fā)現(xiàn)違反了AVL樹規(guī)則的就需要旋轉(zhuǎn)處理

(2)如何向上更新:

  • 更新的方式是沿著祖先路徑更新(回溯)
  • 將parent結(jié)點更新到它的_parent位置上,將cur結(jié)點更新到它的_parent位置上
  • 在這個過程中一旦發(fā)現(xiàn)有違反AVL樹規(guī)則的時即parent的平衡因子變成2或-2時
  • 這時就需要進行旋轉(zhuǎn)處理

具體過程如下:

  • 子樹高度變了,就要繼續(xù)往上更新
  • 子樹的高度不變, 則更新完成
  • 子樹違反平衡規(guī)則,則停止更新, 旋轉(zhuǎn)子樹

情況一:

  • 原來是 1 or -1 -->0 (插入結(jié)點填在了矮的那一邊)

在這里插入圖片描述
在這里插入圖片描述

情況二:

  • 原來是 0 -->1 or -1 (插入結(jié)點導致一邊變高了)

在這里插入圖片描述

  • 當parent->_bf == 1時

在這里插入圖片描述

  • 當parent->_bf == -1時

在這里插入圖片描述

情況三:

  • 一定要檢查,不保證其他地方不會出現(xiàn)錯誤
    在這里插入圖片描述

我們討論問題要將各個方面的都要考慮到位才行,即使前面都正確是不會走到這一步的,但是為了萬無一失還是要將這一步寫上。

情況四:

  • 當插入的位置滿了時

在這里插入圖片描述
具體代碼如下:

bool Insert(const pair& kv)
{if (_root == nullptr)
	{_root = new Node(kv);
		_root->_bf = 0;
		return true;
	}

	Node* parent = nullptr;
	Node* cur = _root;

	while (cur)
	{if (cur->_kv.first< kv.first)
		{	parent = cur;
			cur = cur->_right;
		}
		else if (cur->_kv.first >kv.first)
		{	parent = cur;
			cur = cur->_left;
		}
		else
		{	return false;
		}
	}

	//找到符合規(guī)則的位置之后再插入
	cur = new Node(kv);
	if (parent->_kv.first< kv.first)
	{parent->_right = cur;
	}
	else
	{parent->_left = cur;
	}
	//三叉鏈的鏈接 -- 鏈上父節(jié)點
	cur->_parent = parent;
	
	while (parent)
	{if (cur == parent->_right)
		{	parent->_bf++;
		}
		else if (cur == parent->_left)
		{	parent->_bf--;
		}

		//是否繼續(xù)更新
		if (parent->_bf == 0) //原來是 1 or -1 -->0 (插入結(jié)點填在了矮的那一邊)
		{	//高度不變,更新結(jié)束
			break;
		}
		else if(parent->_bf == 1 || parent->_bf == -1)
			//原來是 0 -->1 or -1 (插入結(jié)點導致一邊變高了)
		{	//子樹的高度變了,繼續(xù)更新祖先
			cur = cur->_parent;
			parent = parent->_parent;
		}
		else if (parent->_bf == 2 || parent->_bf == -2)
			//原來是 1 or -1 -->2 or -2 (插入結(jié)點導致本來高的一邊又變高了)
		{	//子樹不平衡了 -- 需要旋轉(zhuǎn)處理(左單旋的特征 -- 右邊高)
			if (parent->_bf == 2 && cur->_bf == 1)//左單旋
			{		RatateL(parent);
			}
			//子樹不平衡了 -- 需要旋轉(zhuǎn)處理(右單旋的特征 -- 左邊高)
			else if (parent->_bf == -2 && cur->_bf == -1)//右單旋
			{		RatateR(parent);
			}
			else if (parent->_bf == -2 && cur->_bf == 1)//左右雙旋
			{		RatateLR(parent);
			}
			else if (parent->_bf == 2 && cur->_bf == -1)//右左雙旋
			{		RatateRL(parent);
			}
			//旋轉(zhuǎn)完之后ppNode為根的子樹高度不變 -- 所以對ppNode的平衡因子沒有影響
			break;
		}
		else // 一定要檢查 -- 不保證其他地方不會出現(xiàn)錯誤
		{	//插入之前AVL數(shù)就存在平衡子樹,|平衡因子| >= 2結(jié)點
			assert(false);
		}
	}

	return true;
}

2.3 AVL樹的旋轉(zhuǎn)操作:(重點)

上述我們已經(jīng)闡述了,在什么情況下需要對AVL樹進行旋轉(zhuǎn)操,接下來我們就來講一下具體的旋轉(zhuǎn)步驟。

旋轉(zhuǎn)原則:

  • 保持搜索樹的規(guī)則
  • 子樹變平衡

旋轉(zhuǎn)一共分為四種旋轉(zhuǎn)方式:

  • 左單旋、右單旋
  • 左右雙旋、右左雙旋
2.3.1 左單旋

當右子樹高的時候,這時就要向左旋轉(zhuǎn)。

旋轉(zhuǎn)過程:

  • 將要旋轉(zhuǎn)的子樹的根節(jié)點設為parent,根結(jié)點的右子樹為subR,subR的左節(jié)點為subRL
  • 將subRL給parent的右,再將parent給subR的左
  • 改變其鏈接關(guān)系即可
  • 這樣一來subR做了子樹的根,根結(jié)點的左右子樹高度差從2變成了0

旋轉(zhuǎn)詳情圖:

在這里插入圖片描述

  • 代表所有情況的抽象圖、長方形條表示的是子樹

原理:

  • 左旋轉(zhuǎn)的目的是將左子樹變高
  • 本來右子樹高,向左旋轉(zhuǎn)之后,將左子樹和右子樹變得一樣高
  • 根節(jié)點的右子樹的所有結(jié)點肯定比根結(jié)點大
  • 所以subRL可以放在parent->left
  • subRL肯定比subR小,parent也比subR小
  • 所以都可以放在subR的左邊

代表所有情況的抽象圖長方形條表示的是子樹

下面來討論一下h

  • h == 0時
    在這里插入圖片描述
  • h == 1時

此時有兩種情況,新增的節(jié)點有可能是鏈接在這棵樹最右邊結(jié)點的,左邊也有可能是鏈接在右邊

在這里插入圖片描述

  • h == 2時

此時一共有36種情況

在這里插入圖片描述
解釋:

  • 因為是左單旋,所以這棵樹的最左邊一定是高度為二的滿二叉樹
  • 不然要是用其余兩種非滿二叉樹的情況,肯可能空白的子樹都已經(jīng)不滿足AVL樹了,局部子樹就要旋轉(zhuǎn)了
  • 一共有三個位置插入空白子樹,最后一個已經(jīng)定下來了,所以還剩兩個位置
  • 剩下的兩個位置每個位置能有三種空白子樹可以選擇,所以是3 * 3一共9種
  • 固定下來的空白子樹可以新增結(jié)點的位置一共有4處
  • 所以綜上所述一共有9 * 4一共36種
  • h == 3時
    ……
  • 此時一定是比h == 2時候的情況更復雜
  • 所以層數(shù)越高,情況就越多
  • 所以我們用長方形代替了子樹

具體代碼如下:

//右邊高就要左旋轉(zhuǎn)
//左單旋
void RatateL(Node* parent)
{Node* subR = parent->_right;
	Node* subRL = subR->_left;

	parent->_right = subRL;
	if (subRL != nullptr)
	{subRL->_parent = parent;
	}

	Node* ppNode = parent->_parent;

	subR->_left = parent;
	parent->_parent = subR;

	if (parent == _root)
	{_root = subR;
		_root->_parent = nullptr;
	}
	else
	{if (parent == ppNode->_left)
		{	ppNode->_left = subR;
		}
		else if(parent == ppNode->_right)
		{	ppNode->_right = subR;
		}

		subR->_parent = ppNode;
	}

	//更新平衡因子
	parent->_bf = 0;
	subR->_bf = 0;
}

同時代碼的一些細節(jié)也是需要把控的

  • subRL可能是nullptr空指針,要加以判斷不然會引起非法訪問
  • parent有可能就是樹的根,parent也有可能只是整個樹的局部子樹
  • 所以要將parent->_parent先保存起來,方便之后的鏈接工作

在這里插入圖片描述


2.3.2 右單旋

當左子樹高的時候,這時就要向右旋轉(zhuǎn)。

旋轉(zhuǎn)詳情圖:

在這里插入圖片描述
旋轉(zhuǎn)過程:

  • 過程和原理與左單旋過程類似,可以參考左單旋過程

與左單旋一樣當討論h時,也能分出很多種,h = 1時是2種,h = 2時36種。

具體代碼如下:

//左邊高就要右旋轉(zhuǎn)
//右單旋
void RatateR(Node* parent)
{Node* subL = parent->_left;
	Node* subLR = subL->_right;

	parent->_left = subLR;
	if (subLR != nullptr)
	{subLR->_parent = parent;
	}

	Node* ppNode = parent->_parent;

	subL->_right = parent;
	parent->_parent = subL;

	if (parent == _root)
	{_root = subL;
		subL->_parent = nullptr;
	}
	else
	{if (ppNode->_left == parent)
		{	ppNode->_left = subL;
		}
		else if (ppNode->_right == parent)
		{	ppNode->_right = subL;
		}
		subL->_parent = ppNode;
	}
	//更新平衡因子
	subL->_bf = 0;
	parent->_bf = 0;
}

細節(jié)把控也與左單旋類似可以參考左單旋。


2.3.3 左右雙旋

光有左右單旋是解決不了所有問題的,如圖所示就是特殊情況:

在這里插入圖片描述
如圖所示,很顯然右單旋并沒有解決問題,旋轉(zhuǎn)之后仍然不是AVL樹,此時我們就引入了雙旋:

旋轉(zhuǎn)詳情圖:

在這里插入圖片描述
同樣可以對h進行討論,也會對應很多種情況,不再一 一贅述。

具體代碼如下:

//左右雙旋
void RatateLR(Node* parent)
{Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int bf = subLR->_bf;

	RatateL(parent->_left);
	RatateR(parent);

	//更新平衡因子 -- 全是0的情況也要單獨寫,不要依賴單旋
	if (bf == 0)
	{parent->_bf = 0;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
	else if (bf == 1)
	{parent->_bf = 0;
		subL->_bf = -1;
		subLR->_bf = 0;
	}
	else if (bf == -1)
	{parent->_bf = 1;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
	else
	{//subLR->_bf旋轉(zhuǎn)前就有問題
		assert(false);
	}
}
  • 我們在實現(xiàn)雙旋的時候可以復用單旋
  • 但是單旋有個坑,會出現(xiàn)將平衡因子搞成0的情況

兩種解決方案:

  • 將單旋中更新的平衡因子拿出來
  • 旋轉(zhuǎn)之前將位置記錄下來

我們采用第一種方法,單獨將平衡因子拿出來處理。


2.3.4 右左雙旋

旋轉(zhuǎn)詳情圖:

在這里插入圖片描述
同樣的右左雙旋和左右雙旋差不多,可以參考上文。

具體代碼如下:

//右左雙旋
void RatateRL(Node* parent)
{Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;

	RatateR(parent->_right);
	RatateL(parent);

	if (bf == 0)
	{subRL->_bf = 0;
		parent->_bf = 0;
		subR->_bf = 0;
	}
	else if (bf == 1)
	{subRL->_bf = 0;
		parent->_bf = -1;
		subR->_bf = 0;
	}
	else if (bf == -1)
	{subRL->_bf = 0;
		parent->_bf = 0;
		subR->_bf = 1;
	}
	else
	{//subLR->_bf旋轉(zhuǎn)前就有問題
		assert(false);
	}
}

3. 驗證AVL樹

我們先增加幾個成員函數(shù):

1.層序遍歷打印樹

void levelOrder()
{vector>vv;
	if (_root == nullptr)
	{return;
	}

	queueq;
	int levelSize = 1;
	q.push(_root);

	while (!q.empty())
	{//levelSize控制一層一層出
		vectorlevelV;
		while (levelSize--)
		{	Node* front = q.front();
			q.pop();
			levelV.push_back(front->_kv.first);

			if (front->_left != nullptr)
			{		q.push(front->_left);
			}

			if (front->_right != nullptr)
			{		q.push(front->_right);
			}
		}
		vv.push_back(levelV);

		for (auto e : levelV)
		{	cout<< e<< " ";
		}
		cout<< endl;

		//上一層出完,下一層就都進隊列
		levelSize = q.size();
	}
}

2.中序遍歷二叉樹:

void _InOrder(Node* root)
{if (root == nullptr)
		return;

	_InOrder(root->_left);
	cout<< root->_kv.first<< " ";
	_InOrder(root->_right);
}

void InOrder()
{_InOrder(_root);
	cout<< endl;
}

3.求二叉樹高度:

int _Height(Node* root)
{if (root == nullptr)
	{return 0;
	}

	//后續(xù)的方式
	int lh = _Height(root->_left);
	int rh = _Height(root->_right);

	return lh >rh ? lh + 1 : rh + 1;
}

int Height()
{return _Height(_root);
}

驗證一:

void TestAVLTree()
{//升序 -- 右邊高左單旋
	//int arr[] = { 1,2,3,4,5,6,7,8 };

	//降序 -- 左邊高右單旋
	int arr[] = {8,7,6,5,4,3,2,1 };

	AVLTreet;
	for (auto e : arr)
	{t.Insert(make_pair(e, e));
	}

	t.levelOrder();
}

在這里插入圖片描述
如圖所示的兩棵樹均是滿足AVL樹,但是這這種驗證還是不太嚴謹。

3.1 嚴格驗證AVL樹:
  • 在插入一個結(jié)點之后,驗證該棵樹的每一棵子樹是否都滿足AVL樹的規(guī)則:
bool _IsBalanceTree(Node* root)
{//空樹也是AVL樹
	if (nullptr == root)
		return true;

	//計算pRoot節(jié)點的平衡因子:即pRoot左右子樹的高度差
	int leftHeight = _Height(root->_left);
	int rightHeight = _Height(root->_right);

	//求差值
	int diff = rightHeight - leftHeight;

	//如果計算出的平衡因子與pRoot的平衡因子不相等,或者
	//pRoot平衡因子的絕對值超過1,則一定不是AVL樹
	if (abs(diff) >= 2)
	{cout<< root->_kv.first<< "結(jié)點平衡因子異常"<< endl;
		return false;
	}

	//平衡因子沒有異常但是和結(jié)點的對不上
	if (diff != root->_bf)
	{//說明更新有問題
		cout<< root->_kv.first<< "結(jié)點平衡因子不符合實際"<< endl;
		return false;
	}

	//pRoot的左和右如果都是AVL樹,則該樹一定是AVL樹
	//把自己和自己的左右子樹都檢查了,遞歸檢查
	return _IsBalanceTree(root->_left)
		&& _IsBalanceTree(root->_right);
}

bool IsBalanceTree()
{return _IsBalanceTree(_root);
}
  • 通過遞歸的方式將整棵樹的子樹都驗證一遍。

驗證二:

  • 我們插入隨機值,這樣更具有普遍性
  • 順序插入我們也順便實驗一下
void TestAVLTree()
{const size_t N = 1024 * 1024 * 10;
	vectorarr;
	arr.reserve(N);//避免頻繁擴容

	srand(time(0));
	for (size_t i = 0; i< N; i++)
	{arr.push_back(rand());
		//arr.push_back(i);
	}

	AVLTreet;
	for (auto e : arr)
	{t.Insert(make_pair(e, e));
	}

	cout<< "是否平衡?"<< t.IsBalanceTree()<< endl;
	cout<< "高度:"<< t.Height()<< endl;

	//t.InOrder();
}

在這里插入圖片描述

你是否還在尋找穩(wěn)定的海外服務器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調(diào)度確保服務器高可用性,企業(yè)級服務器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧


標題名稱:【C++、數(shù)據(jù)結(jié)構(gòu)】AVL樹模擬實現(xiàn)-創(chuàng)新互聯(lián)
分享URL:http://weahome.cn/article/dcssii.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部