C++学习笔记(四)指针实现的链表、堆栈、队列、二叉查找树

一、概述

本文将介绍在程序执行期间动态消长的动态数据结构,包括链表(linked list)、栈(stack)、队列(queue)、二叉树(binary tree)。这些动态数据结构与定长数据结构(数组)的区别在于前者的长度是动态分配的,而后者为固定长度。

二、链表

链表是多个数据节点(node)的线性集合,这些节点通过指针链(link)链接起来,是一种线性数据结构。在链表的任何一项数据项上都可以进行插入和删除操作。

(1)自引用类

链表的节点就是自用类对象,所以先理解自引用类的概念。一个自引用类包含一个指向它同类的对象的指针成员,以下即为一个自引用类,如下所示。

class Node
{
public:
	Node(int);
private:
	int element;
	Node *nextPtr;    //指向同类对象的指针
};

一个Node类对象的nextPtr指针可以指向下一个Node类对象,这样自引用类对象可以由自己的指针链接成有用的数据结构,除了链表,还有队列、栈、树等。

(2)链表的结构

链表为线性结构,可以想象为一些数据节点排列成一行,然后链表具有指向头节点的指针firstPtr和指向尾节点的指针lastPtr,头节点的指针指向下一个节点,下一个节点的指针指向再下一个节点,直至最后一个节点,而尾节点的指针则置为空(0),表示链表的结束。结构可由下图所示。

《C++学习笔记(四)指针实现的链表、堆栈、队列、二叉查找树》

(3)下面由模板类list类和模板类NodeInList类构成链表结构,NodeInList类的对象作为节点这一数据结构,而list类对象用指针将多个节点链接起来。

//NodeInList.h
#ifndef NODEINLIST_H
#define NODEINLIST_H

template<typename T> class List;//List类存在便于在下面类中声明为友元类

template<typename T>
class NodeInList
{
	friend class List<T>;      //声明List类为其友元类

public:
	NodeInList(const T &);
	T getElement() const;
private:
	T element;
	NodeInList<T> *nextPtr;    //指向下一个节点的指针
};

template<typename T>
NodeInList<T>::NodeInList(const T &data)
	:element(data),nextPtr(0)
{

}

template<typename T>
T NodeInList<T>::getElement()const
{
	return element;
}

#endif
//List.h
#ifndef LIST_H
#define LIST_H

#include <iostream>
using std::cout;

#include "NodeInList.h"  //包含NodeInList类定义的头文件

template<typename T>
class List
{
public:
	List();
	~List();
	void inSertAtFront(const T &);  //链表头插入数据
	void insertAtBack(const T &);   //链表末插入数据
	bool removeFromFront(T &);      //从链表头删除数据
	bool removeFromBack(T &);       //从链表末删除数据   
	bool isEmpty()const;            //检测是否为空
	void print() const;              //打印链表中的数据
private:
	NodeInList<T> *firstPtr;        //指向链表头节点的指针
	NodeInList<T> *lastPtr;         //指向链表末节点的指针

	NodeInList<T> *createNewNode(const T &);  //开辟节点空间
};

template<typename T>
List<T>::List()
	:firstPtr(0),lastPtr(0)
{

}

template<typename T>
List<T>::~List()
{
	if(!isEmpty())
	{
		cout<<"Destroying nodes!\n";
		NodeInList<T> *currentPtr = firstPtr;
		NodeInList<T> *tempPtr;

		while( currentPtr != 0)       //如果当前指针不为空
		{
			tempPtr =currentPtr;
			cout<<tempPtr->element<<'\n';  //输出当前指针的指向节点的数据
			currentPtr =currentPtr->nextPtr;   //当前指针指向再下一个节点
			delete tempPtr;                    //删除之前的指针     
		}
	}
	cout<<"All nodes destroyed\n\n";
}

template<typename T>
void List<T>::inSertAtFront( const T& value)
{
	NodeInList<T> *newPtr = createNewNode( value);

	if(isEmpty())
	{
		firstPtr = lastPtr =newPtr;
	}
	else
	{
		newPtr->nextPtr = firstPtr;  //新节点的指针指向头节点
		firstPtr = newPtr;           //firstPtr指针指向新节点
	}
}


template<typename T>
void List<T>::insertAtBack(const T &value )
{
	NodeInList<T> *newPtr = createNewNode( value);

	if(isEmpty())
	{
		firstPtr = lastPtr =newPtr;
	}
	else
	{
		lastPtr->nextPtr =newPtr;   //新节点的指针指向末节点
		lastPtr = newPtr;           //lastPtr指向新节点
	}
}

template<typename T>
bool List<T>::removeFromFront(T &value)
{
	if(isEmpty())
		return false;
	else
	{
		NodeInList<T> *tempPtr =firstPtr;

		if(firstPtr == lastPtr)
			firstPtr = lastPtr =0;
		else
			firstPtr = firstPtr->nextPtr;
		value = tempPtr->element;
		delete tempPtr;
		return true;
	}
}

template<typename T>
bool List<T>::removeFromBack(T &value)
{
	if(isEmpty())
		return false;
	else
	{
		NodeInList<T> *tempPtr =lastPtr;
		if(firstPtr == lastPtr)
			firstPtr = lastPtr =0;
		else
		{
		NodeInList<T> *currentPtr =firstPtr;

		while(currentPtr->nextPtr != tempPtr)
			currentPtr =currentPtr->nextPtr;

		lastPtr = currentPtr;
		currentPtr->nextPtr =0 ;
		}
		value =tempPtr->element;
		delete tempPtr;
		return true;
	}
}

template<typename T>
bool List<T>::isEmpty()const
{
	return firstPtr == 0;
}

template<typename T>
NodeInList<T>* List<T>::createNewNode(const T &value)
{
	return new NodeInList<T>(value);
}

template<typename T>
void List<T>::print()const
{
	if(isEmpty())
	{
		cout<<"The list is empty\n\n";
		return;
	}

	NodeInList<T> *currentPtr = firstPtr;
	cout<<"The list is: ";

	while(currentPtr != 0)
	{
		cout<<currentPtr->element<<" ";
		currentPtr = currentPtr->nextPtr;
	}

	cout<<"\n\n";
}

#endif
///////////////testList//////////////////
#include <iostream>
using std::cin;
using std::cout;
using std::endl;

#include <string>
using std::string;

#include "List.h"

template<typename T>
void testList( List<T> &listObject)
{
	cout<<"Enter one of the following:\n"
		<<" 1 to insert at beginning of list\n"
		<<" 2 to insert at end of list\n"
		<<" 3 to delete from beginning of list\n"
		<<" 4 to delete from end of list\n"
		<<" 5 to end list processing\n";

	int choice;
	T value;

	do 
	{
		cout<<"?";
		cin>>choice;
		switch(choice)
		{
		case 1:
			cout<<"input value:";
			cin>>value;
			listObject.inSertAtFront(value);
			listObject.print();
			break;
		case 2:
			cout<<"input value:";
			cin>>value;
			listObject.insertAtBack(value);
			listObject.print();
			break;
		case 3:
			if(listObject.removeFromFront(value))
				cout<<value<<" removed from list\n";
			listObject.print();
			break;
		case 4:
			if(listObject.removeFromBack(value))
				cout<<value<<" removed from list\n";
			listObject.print();
			break;
		}
	} while ( choice != 5);
	cout<<"End list test\n\n";
}

int main()
{
	List<int> intList;
	testList(intList );

	List<double> doubleList;
	testList(doubleList);
	return 0;
}

测试结果如下

《C++学习笔记(四)指针实现的链表、堆栈、队列、二叉查找树》

《C++学习笔记(四)指针实现的链表、堆栈、队列、二叉查找树》

(4)循环单向链表及双向链表

前面讨论的链表仅为单向链表,特点是只能做单向遍历;如果将上述链表末节点的指针指向首节点,形成单向的一个回路,这样就构成了循环单向链表;如果每个节点不但包含指向下一个节点的指针,还包含指向前一个节点的指针,并构成了一个你像逆向回路,那么则构成了双向链表,双向链表的特点是可向前和向后遍历。如下图所示,左边为单向循环链表,右边为双向循环链表

《C++学习笔记(四)指针实现的链表、堆栈、队列、二叉查找树》《C++学习笔记(四)指针实现的链表、堆栈、队列、二叉查找树》

三、栈

(1)栈是编译器和操作系统中重要的数据结构,元素的插入和删除只能在栈栈顶进行。栈也可以看成是有限制的链表结构,只是元素的增加和删除只能在链表头进行,也即是后进先出的结构。下面模板类Stack类对模板类List类private继承(即基类所有数据成员和成员函数均为派生类的私有成员),然后使Stack类的成员函数适当调用List类的成员函数,push调用insertAtFront,pop调用removeFromFront,isStackEmpty调用isEmpty,而printStack调用print,这种方式称为委托。

<span style="font-size:12px;">//Stack.h
#ifndef STACK_H
#define STACK_H

#include "List.h"

template<typename STACKTYPE>
class Stack:private List<STACKTYPE>
{
public:
	void push(const STACKTYPE &value)
	{
		inSertAtFront(value);
	}

	bool pop(STACKTYPE &value)
	{
		return removeFromFront(value);
	}

	bool isStackEmpty()const
	{
		return isEmpty();
	}

	void printStack()const
	{
		print();
	}
};
#endif</span>
///////////////testStack//////////////////
#include <iostream>
using std::cout;
using std::endl;

#include "Stack.h"

int main()
{
	Stack<int > intStack;    //存放整型变量的栈

	for(int i=0;i<3 ;i++)
	{
		intStack.push(i);
		intStack.printStack();
	}

	int popIntValue;

	while( !intStack.isStackEmpty())
	{
		intStack.pop(popIntValue);
		cout<<popIntValue<<" popped from stack"<<endl;
		intStack.printStack();
	}

	Stack<double> doubleStack;   //存放双浮点型型变量的栈
	double value =1.1;

	for (int j=0;j<3;j++)
	{
		doubleStack.push(value);
		doubleStack.printStack();
		value +=1.1;
	}

	double popDoubleValue;

	while(!doubleStack.isStackEmpty())
	{
		doubleStack.pop(popDoubleValue);
		cout<<popDoubleValue<<" popped from stack"<<endl;
		doubleStack.printStack();
	}
	
	return 0;
}

(2)测试结果

《C++学习笔记(四)指针实现的链表、堆栈、队列、二叉查找树》

四、队列

队伍是一种模拟了排队等候的数据结构,插入操作在队伍的后面(对尾)进行,删除操作则在队伍的前面(队列头)进行,也即是先进先出。队列也可以看成是有限制的链表结构,只是元素的增加只在链表头,而删除元素只在链表后。下面通过对List类模板private继承得到Queue类,使Queue类的成员函数适当调用List类的成员函数,enqueue调用insertAtBack,dequeue调用removeFromFront,isQueueEmpty调用isEmpty,而printQueue调用print函数。

//Queue.h
#ifndef QUEUE_H
#define QUEUE_H

#include "List.h"

template <typename QUEUETYPE>
class Queue:private List<QUEUETYPE>
{
public:
	void enqueue(const QUEUETYPE&value)
	{
		insertAtBack(value);
	}

	bool deququ(QUEUETYPE &value)
	{
		return removeFromFront(value);
	}

	bool isQueueEmpty()const
	{
		return isEmpty();
	}

	void printQueue()const
	{
		print();
	}
};


#endif
//////////////////////testQueue////////////////////

#include <iostream>
using std::cout;
using std::endl;

#include "Queue.h"

int main()
{
	Queue<int > intQueue;

	for (int i=0;i<3;i++)
	{
		intQueue.enqueue(i);
		intQueue.printQueue();
	}

	int dequeueIntValue;

	while(!intQueue.isQueueEmpty())
	{
		intQueue.deququ(dequeueIntValue);
		cout<<dequeueIntValue<<" dequeued from Queue"<<endl;
		intQueue.printQueue();
	}

	Queue<double> doubleQueue;
	double value = 1.1;

	for (int j=0;j<3;j++)
	{
		doubleQueue.enqueue(value);
		doubleQueue.printQueue();
		value +=1.1;
	}

	double deququDoubleValue;

	while(!doubleQueue.isQueueEmpty())
	{
		doubleQueue.deququ(deququDoubleValue);
		cout<<deququDoubleValue<<" dequeued form Queue"<<endl;
		doubleQueue.printQueue();
	}

	return 0;
}

测试结果

《C++学习笔记(四)指针实现的链表、堆栈、队列、二叉查找树》

五、二叉树

(1)二叉树的结构

链表、栈和队列均为线性结构,而树是非线性的,树的节点可以可以包含两个或更多个指针链接。这里讨论的是二叉树,即所有的节点只包含两个链接。二叉树的结构可如下图所示。

《C++学习笔记(四)指针实现的链表、堆栈、队列、二叉查找树》

图中有A、B、C、D、E共5个节点,每个节点除了自身的数据变量还有两个指向下一节点的指针,根指针rootPtr指向了根节点,父节点的指针指向了子节点,例如图上B和C的父节点是A,D的父节点是B,E的父节点是C。插入节点是从根节点开始的,向下插入,这与树的生长方向相反。

(2)二叉查找树

这是一种特殊的二叉树,也称为二叉搜索树,二叉查找树没有值相同的节点,因为如果有相同值的节点话,这个节点会无法进行比较任何,进而无法确定是插在左边还是右边。左子树上的值都小于其父节点的值,而它的任何右子树上的值都大于其父节点的值。如下图所示。

《C++学习笔记(四)指针实现的链表、堆栈、队列、二叉查找树》

(3)二叉查找树程序

实现二叉查找树的代码如下,并且使用了三种方法遍历,前序遍历、中序遍历、后序遍历。

//TreeNode.h
#ifndef TREENODE_H
#define TREENODE_H

template<typename T> class Tree;

template<typename T>
class TreeNode
{
	friend class Tree<T>;
public:
	TreeNode(const T &d)
		:leftPtr(0),data(d),rightPtr(0)
	{

	}

	T getData()const
	{
		return data;
	}
private:
	TreeNode<T> *leftPtr;
	T data;
	TreeNode<T> *rightPtr;
};

#endif
//Tree.h
#ifndef TREE_H
#define TREE_H

#include <iostream>
using std::cout;
using std::endl;

#include "TreeNode.h"

template<typename T>
class Tree
{
public:
	Tree();
	void insertNode(const T &);
	void preOrderTraversal() const;
	void inOrderTraversal()const;
	void postOrderTraversal()const;
private:
	TreeNode<T> *rootPtr;

	void insertNodeHelper(TreeNode<T> **,const T &);
	void preOrderHelper(TreeNode<T> *)const;
	void inOrderHelper(TreeNode<T> *)const;
	void postOrderHelper(TreeNode<T> *)const;
};

template < typename T>
Tree<T>::Tree()
{
	rootPtr =0;
}

template < typename T>
void Tree<T>::insertNode(const T &value)
{
	insertNodeHelper(&rootPtr ,value);
}

template < typename T>
void Tree<T>::insertNodeHelper(TreeNode<T> **ptr ,const T &value)
{
	if(*ptr == 0)
		*ptr = new TreeNode<T> (value);
	else
	{
		if(value<(*ptr)->data)
			insertNodeHelper(&((*ptr)->leftPtr ) ,value);
		else
		{
			if (value >(*ptr)->data )
				insertNodeHelper(&((*ptr)->rightPtr ) ,value);
			else
				cout<<value<<" dup"<<endl;
		}

	}
}

template < typename T>
void Tree<T>::preOrderTraversal()const
{
	preOrderHelper( rootPtr);
}

template < typename T>
void Tree<T>::preOrderHelper(TreeNode<T> *ptr)const
{
	if(ptr != 0)
	{
		cout<<ptr->data<<" ";
		preOrderHelper(ptr->leftPtr);
		preOrderHelper(ptr->rightPtr);
	}
}

template < typename T>
void Tree<T>::inOrderTraversal()const
{
	inOrderHelper(rootPtr);
}

template < typename T>
void Tree<T>::inOrderHelper(TreeNode<T> *ptr )const
{
	if(ptr != 0)
	{
		inOrderHelper(ptr->leftPtr);
		cout<<ptr->data<<" ";
		inOrderHelper(ptr->rightPtr);
	}
}

template < typename T>
void Tree<T>::postOrderTraversal()const
{
	postOrderHelper(rootPtr);
}

template < typename T>
void Tree<T>::postOrderHelper(TreeNode<T> *ptr )const
{
	if(ptr != 0)
	{
		postOrderHelper(ptr->leftPtr);
		postOrderHelper(ptr->rightPtr);
		cout<<ptr->data<<" ";
	}
}
#endif
///////////////////testTreenode//////////////////////////
#include <iostream>
using std::cout;
using std::cin;
using std::fixed;

#include <iomanip>
using std::setprecision;

#include "Tree.h"

int main()
{
	Tree<int> intTree;
	int intValue;

	cout<<"Enter 10 integer values:\n";

	for(int i=0;i <10 ;i++)
	{
		cin>>intValue;
		intTree.insertNode( intValue);
	}

	cout<<"前序遍历:\n";
	intTree.preOrderTraversal();

	cout<<"中序遍历:\n";
	intTree.inOrderTraversal();

	cout<<"后序遍历:\n";
	intTree.postOrderTraversal();
}

测试结果

《C++学习笔记(四)指针实现的链表、堆栈、队列、二叉查找树》

当依次输入50,25,75,12,33,67,88,6,13,68时,则按照二叉查找树的规则,会得到的结构如下所示:

《C++学习笔记(四)指针实现的链表、堆栈、队列、二叉查找树》

前序遍历、中序遍历和后序遍历的结果如测试结果所示,详细过程可看preOrderHelper

、inOrderHelper、postOrderHelper函数

(4)二叉查找树的优点

在二叉查找树中查找匹配的关键字是很迅速的,假如树为平衡的,那么每个分支包含树上一半数目的节点,为搜索关键值,每次在一个节点上的比较就能排除一半的节点,称为O(log n)算法,那么有n个元素的二叉查找树最多需要log2 n次的比较就可以找到匹配的值或确定不存在。

本文的学习资料参考自《Cpp大学教程(第五版)》第21章,阅读即可获得更详细的解释。

    原文作者:二叉查找树
    原文地址: https://blog.csdn.net/ap1005834/article/details/47984385
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞