1. 程式人生 > >C++面試題之資料結構和演算法

C++面試題之資料結構和演算法

 

C++面試題之資料結構和演算法

目錄

1、String原理及實現

2、連結串列的實現

2.1、順序連結串列

2.2、鏈式表

2.3、雙鏈表

2.4、迴圈連結串列

3、佇列

3.1、順序佇列

3.2、鏈式佇列

4、棧

4.1、順序棧

4.2、鏈式棧

5、二叉樹

5.1、二叉樹的鏈式儲存

5.2、哈夫曼樹

6、查詢演算法

6.1、線性表查詢(順序查詢、折半查詢)

6.2、樹表查詢(二叉排序樹、平衡二叉樹、B-樹、B+樹)

6.3、雜湊表查詢

7、排序演算法

7.1、直接插入排序

7.2、折半插入排序

7.3、希爾排序

7.4、氣泡排序

7.5、快速排序

7.6、直接選擇排序

7.7、堆排序

7.8、歸併排序

7.9、基數排序

8、圖

8.1、圖的基本概念

8.2、圖的遍歷

8.3、生成樹和最小生成樹

8.4、最短路徑

狄克斯特拉演算法圖解

9、串的模式匹配

9.1、BF演算法

9.2、KMP演算法

10、分治演算法

11、動態規劃演算法

12、回溯法


1、String原理及實現

       string類是由模板類basic_string<class _CharT,class _traits,class _alloc>例項化生成的一個類。basic_tring是由_String_base繼承而來的。

typedef basic_string<char> string

       而實際面試由於時間關係,一般不會要求很詳細的string的功能,一般要求是實現建構函式,拷貝建構函式,賦值函式,解構函式等部分,因為string裡面涉及動態記憶體管理,預設的拷貝建構函式在執行只會進行淺複製,這樣會造成兩個物件指向一塊區域記憶體的物件。如果一個物件被銷燬,會造成另外一個物件執行出錯,這時要進行深拷貝。

#pragma once
#include<iostream>
class String
{
private:
	char*  data;   //字串內容
	size_t length; //字串長度

public:
	String(const char* str = nullptr);  //通用建構函式
	String(const String& str);          //拷貝建構函式
	~String();                          //解構函式

	String operator+(const String &str) const;  //過載+
	String& operator=(const String &str);       //過載=
	String& operator+=(const String &str);      //過載+=
	bool operator==(const String &str) const;   //過載==

        friend std::istream& operator>>(std::istream &is, String &str);//過載>>
	friend std::ostream& operator<<(std::ostream &os, String &str);//過載<<

	char& operator[](int n)const;               //過載[]

	size_t size() const;                        //獲取長度
	const char* c_str() const;                  //獲取C字串
};
#include"String.h"

//通用建構函式
String::String(const char *str)
{
	if (!str)
	{
		length = 0;
		data = new char[1];  //一定要用new分配記憶體,否則就變成了淺拷貝;
		*data = '\0';
	}
	else
	{
		length = strlen(str); //
		data = new char[length + 1];
		strcpy(data,str);
	}
}

//拷貝建構函式
String::String(const String& str)
{
	length = str.size();
	data = new char[length + 1];  //一定要用new,否則變成了淺拷貝
	strcpy(data,str.c_str());
}

//解構函式
String::~String()
{
	delete[]data;
	length = 0;
}

//過載+
String String::operator+(const String &str) const  
{
	String StringNew;
	StringNew.length = length + str.size();

	StringNew = new char[length + 1];
	strcpy(StringNew.data, data);
	strcat(StringNew.data, str.data);  //字串拼接函式,即將str內容複製到StringNew內容後面
	return StringNew;
}

//過載=
String& String::operator=(const String &str)       
{
	if (this == &str)
	{
		return *this;
	}

	delete []data;                 //釋放記憶體
	length = str.length;
	data = new char[length + 1];
	strcpy(data,str.c_str());
	return *this;
}

//過載+=
String& String::operator+=(const String &str)      
{
	length += str.size();
	char *dataNew = new char[length + 1];
	strcpy(dataNew, data);
	delete[]data;
	strcat(dataNew, str.c_str());
	data = dataNew;
	return *this;
}

//過載==
bool String::operator==(const String &str) const   
{
	if (length != str.length)
	{
		return false;
	}
	return strcmp(data, str.data) ? false : true;
}

//過載[]
char& String::operator[](int n) const           //str[n]表示第n+1個元素   
{
	if (n >= length)
	{
		return data[length - 1]; //錯誤處理
	}
	else
	{
		return data[n];
	}
}

 //獲取長度
size_t String::size() const                      
{
	return this->length;
}

//獲取C字串
const char* String::c_str() const                 
{
	return data;
}

//過載>>

std::istream& operator>>(std::istream &is, String &str)
{
	char tem[1000];
	is >> tem;
	str.length = strlen(tem);
	str.data = new char[str.length + 1];
	strcpy(str.data, tem);
	return is;
}

//過載<<
std::ostream& operator<<(std::ostream &os, String &str)
{
	os << str.c_str();
	return os;
}

       關於operator>>和operator<<運算子過載,我們是設計成友元函式(非成員函式),並沒有設計成成員函式,原因如下:對於一般的運算子過載都設計為類的成員函式,而>>和<<卻不這樣設計,因為作為一個成員函式,其左側運算元必須是隸屬同一個類之下的物件,如果設計成員函式,輸出為物件>>cout >> endl;(Essential C++)不符合習慣。

一般情況下:

將雙目運算子過載為友元函式,這樣就可以使用交換律,比較方便

單目運算子一般過載為成員函式,因為直接對類物件本身進行操作

運算子過載函式可以作為成員函式,友元函式,普通函式。

普通函式:一般不用,通過類的公共介面間接訪問私有成員。

成員函式:可通過this指標訪問本類的成員,可以少寫一個引數,但是表示式左邊的第一個引數必須是類物件,通過該類物件來呼叫成員函式。

友元函式:左邊一般不是物件。<< >>運算子一般都要申明為友元過載函式

2、連結串列的實現

2.1、順序連結串列

最簡單的資料結構,開闢一塊連續的儲存空間,用陣列實現

#pragma once
#ifndef SQLIST_H
#define SQLIST_H

#define MaxSize 50
typedef int DataType;

struct SqList  //順序表相當於一個數組,這個結構體就已經表示了整個順序表
{
	DataType data[MaxSize];
	int length;  //表示順序表實際長度
};//順序表型別定義


void InitSqList(SqList * &L);

//釋放順序表
void DestroySqList(SqList * L);

//判斷是否為空表
int isSqListEmpty(SqList * L);

//返回順序表的實際長度
int SqListLength(SqList * L);

//獲取順序表中第i個元素值
DataType SqListGetElem(SqList * L, int i);

//在順序表中查詢元素e,並返回在順序表哪個位置
int GetElemLocate(SqList * L, const DataType e);

//在第i個位置插入元素
int SqListInsert(SqList *&L, int i, DataType e);

//刪除第i個位置元素,並返回該元素的值
DataType SqListElem(SqList* L, int i);

#endif
#include<iostream>
#include"SqList.h"
using namespace std;

//初始化順序表
void InitSqList(SqList * &L)
{
	L = (SqList*)malloc(sizeof(SqList)); // 開闢記憶體
	L->length = 0;
}

//釋放順序表
void DestroySqList(SqList * L)
{
	if (L == NULL)
	{
		return;
	}
	free(L);
}

//判斷是否為空表
int isSqListEmpty(SqList * L)
{
	if (L == NULL)
	{
		return 0;
	}
	return (L->length == 0);
}

//返回順序表的實際長度
int SqListLength(SqList * L)
{
	if (L == NULL)
	{ 
		cout << "順序表分配記憶體失敗" << endl;
		return 0;
	}
	return L->length;
}

//獲取順序表中第i個元素值
DataType SqListGetElem(SqList * L,int i)
{
	if (L == NULL)
	{
		cout << "No Data in SqList" << endl;
		return 0;
	}
	return L->data[i - 1];
}

//在順序表中查詢元素e,並返回在順序表哪個位置
int GetElemLocate(SqList * L, const DataType e)
{
	if( L == NULL)
	{
		cout << "Empty SqList" << endl;
		return 0;
	}
	int i = 0;
	while(i < L->length && L->data[i] != e)
	{
		i++;
	}
	if (i > L->length)
		return 0;
	return i + 1;
}

//在第i個位置插入元素
int SqListInsert(SqList *&L, int i, DataType e)
{
	if(L == NULL)
	{
		cout << "error" << endl;
		return 0;
	}
	if (i > L->length + 1 || i < 1)
	{
		cout << "error" << endl;
		return 0;
	}
	for (int j = L->length; j>=i - 1; j--) //將i之後的元素後移,騰出空間
	{
		L->data[j] = L->data[j - 1];
	}
	L->data[i] = e;
	L->length++;
	return 1;
}

//刪除第i個位置元素,並返回該元素的值
DataType SqListElem(SqList* L, int i)
{
	if (L == NULL)
	{
		cout << "error" << endl;
		return 0;
	}
	if (i < 0 || i > L->length)
	{
		cout << "error" << endl;
		return 0;
	}
	DataType e = L->data[i - 1];
	for (int j = i; j < L->length;j++)
	{
		L->data[j] = L->data[j + 1];
	}
	L->length--;
	return e;
}

2.2、鏈式表

#pragma once
#ifndef LINKLIST_H
#define LINKLIST_H

typedef int DataType;


//單鏈表:單鏈表是一個節點一個節點構成,
//先定義一個節點,節點為一個結構體,當這些節點連在一起,
// 連結串列為指向頭結點的結構體型指標,即是LinkList型指標

typedef struct LNode  //定義的是節點的型別
{
	DataType data;
	struct LNode *next; //指向後繼節點
}LinkList;


void InitLinkList(LinkList * &L);    //初始化連結串列
void DestroyLinkList(LinkList * L); //銷燬單鏈表
int isEmptyLinkList(LinkList * L);  //判斷連結串列是否為空
int LinkListLength(LinkList * L);   //求連結串列長度
void DisplayLinkList(LinkList * L); //輸出連結串列元素
DataType LinkListGetElem(LinkList * L,int i);//獲取第i個位置的元素值
int LinkListLocate(LinkList * L,DataType e);  //元素e在連結串列的位置
int LinkListInsert(LinkList * &L,int i,DataType e);//在第i處插入元素e
DataType LinkListDelete(LinkList * &L,int i); //刪除連結串列第i處的元素
#endif
#include<iostream>
#include"LinkList.h"
using namespace std;

void InitLinkList(LinkList * &L)    //初始化連結串列
{
	L = (LinkList*)malloc(sizeof(LinkList)); //建立頭結點
	L->next = NULL;
}

void DestroyLinkList(LinkList * L) //銷燬單鏈表
{
	LinkList *p = L, *q = p->next;//建立輔助節點指標

	if(L == NULL)
	{
		return;
	}

	while (q != NULL) //銷燬一個連結串列,必須一個節點一個節點的銷燬
	{
		free(p);
		p = q;
		q = p->next;
	}
	free(p);
}

int isEmptyLinkList(LinkList * L)  //判斷連結串列是否為空
{
	return (L->next == NULL);// 1:空;0:非空
}

int LinkListLength(LinkList * L)  //求連結串列長度,連結串列的長度必須一個節點一個節點的遍歷
{
	LinkList *p = L;

	if (L == NULL)
	{
		return 0;
	}

	int i = 0;
	while (p->next != NULL)
	{
		i++;
		p = p->next;
	}
	return i;
}

void DisplayLinkList(LinkList * L)//輸出連結串列元素
{
	LinkList * p = L->next; //此處一點要指向next,這樣是第一個節點,跳過了頭結點

	while (p != NULL)
	{
		cout << p->data << " ";
		p = p->next;
	}
	cout << endl;
}

DataType LinkListGetElem(LinkList * L, int i)//獲取第i個位置的元素值
{
	LinkList *p = L;

	if (L == NULL || i < 0)
	{
		return 0;
	}

	int j = 0;
	while (j < i  && p->next != NULL)
	{
		j++; p = p->next;
	}

	if (p == NULL)
	{
		return 0;
	}
	else
	{
		return p->data;
	}
}

int LinkListLocate(LinkList * L, DataType e)  //元素e在連結串列的位置
{
	LinkList *p = L;

	if (L == NULL)
	{
		return 0;
	}

	int j = 0;
	while (p->next != NULL && p->data == e)
	{
		j++;
	}
	return j+1;
}

int LinkListInsert(LinkList * &L, int i, DataType e)//在第i處插入元素e
{
	LinkList *p = L,*s;
	int j = 0;

	if (L == NULL )
	{
		return 0;
	}

	while (j < i-1 && p != NULL) //先將指標移到該處
	{
		j++;
		p = p->next;
	}

	s = (LinkList*)malloc(sizeof(LinkList)); //新增一個節點,需開闢一個新的記憶體
	s->data = e;
	s->next = p->next;   //先將下一地址給新節點
	p->next = s;    //將原來的指標指向新節點
	return 1;
}

DataType LinkListDelete(LinkList * &L, int i) //刪除連結串列第i處的元素
{
	LinkList *p = L,*q;  //p用來儲存臨時節點
	DataType e;          //用來存被刪除點的元素
	
	int j = 0;
	while (j < i - 1 && p != NULL) //將p指向第i-1節點
	{
		j++;
		p = p->next;
	}
	
	if (p == NULL)
	{
		return 0;
	}
	else
	{
		q = p->next; //q指向第i個節點*p
		e = q->data; //
		p->next = q->next;//從連結串列中刪除p節點,即是p->next = p->next->next,將第i個節點資訊提取出來
		free(q);  //釋放p點記憶體
		return e;
	}
}

2.3、雙鏈表

#pragma once
#ifndef DLINKLIST_H
#define DLINKLIST_H

typedef int DataType;

typedef struct DLNode
{
	DataType Elem;
	DLNode *prior;
	DLNode *next;
}DLinkList;

void DLinkListInit(DLinkList *&L);//初始化雙鏈表
void DLinkListDestroy(DLinkList * L); //雙鏈表銷燬
bool isDLinkListEmpty(DLinkList * L);//判斷連結串列是否為空
int  DLinkListLength(DLinkList * L);  //求雙鏈表的長度
void DLinkListDisplay(DLinkList * L); //輸出雙鏈表
DataType DLinkListGetElem(DLinkList * L, int i); //獲取第i個位置的元素
bool DLinkListInsert(DLinkList * &L, int i, DataType e);//在第i個位置插入元素e
DataType DLinkListDelete(DLinkList * &L, int i);//刪除第i個位置上的值,並返回其值
#endif
#include<iostream>
#include"DLinkList.h"
using namespace std;


void DLinkListInit(DLinkList *&L)//初始化雙鏈表
{
	L = (DLinkList *)malloc(sizeof(DLinkList)); //建立頭結點
	L->prior = L->next = NULL;
}


void DLinkListDestroy(DLinkList * L) //雙鏈表銷燬
{
	if (L == NULL)
	{
		return;
	}
	DLinkList *p = L, *q = p->next;//定義兩個節點,第一個表示當前節點,第二個表示第二個節點
	while (q != NULL)              //當第二個節點指向null,說明p是最後一個節點,如果不是,則
	{                              //釋放掉p,q就為第一個節點,將q賦給p,p->給q,這樣迭代
		free(p);
		p = q;
		q = p->next;
	}
	free(p);
}


bool isDLinkListEmpty(DLinkList * L)//判斷連結串列是否為空
{
	return L->next == NULL;
}


int  DLinkListLength(DLinkList * L)  //求雙鏈表的長度
{
	DLinkList *p = L;

	if (L == NULL)
	{
		return 0;
	}

	int i = 0;
	while (p->next != NULL)
	{
		i++;
		p = p->next;
	}
	return i;
}

void DLinkListDisplay(DLinkList * L) //輸出雙鏈表
{
	DLinkList *p = L->next;  //跳過頭結點,指向第一個節點
	while (p != NULL)
	{
		cout << p->Elem << "  ";
		p = p->next;
	}
}

DataType DLinkListGetElem(DLinkList * L, int i) //獲取第i個位置的元素
{
	DLinkList *p = L;//指向頭結點

	if (L == NULL)
	{
		cout << "Function DLinkListGetElem" << "連結串列為空表" << endl;
		return 0;
	}

	int j = 0;
	while (p != NULL && j < i) //將指標指向第i個位置處
	{
		j++;
		p = p->next;
	}

	if (p == NULL)
	{
		return 0;
	}
	else
	{
		return p->Elem;
	}
}

bool DLinkListInsert(DLinkList * &L, int i, DataType e)//在第i個位置插入元素e
{
	int j = 0;
	DLinkList *p = L, *s;//其中s節點是表示插入的那個節點,所以要給它開闢記憶體

	while (p != NULL && j < i - 1)  //插入節點前,先找到第i-1個節點
	{
		j++;
		p = p->next;
	}

	if( p == NULL)
	{
		return 0;
	}
	else
	{
		s = (DLinkList *)malloc(sizeof(DLinkList));
		s->Elem = e;
		s->next = p->next;//插入點後繼的指向
		if (p->next != NULL)
		{
			p->next->prior = s;  //插入點的後繼的前驅指向
		}
		s->prior = p; //插入點前驅的前驅指向
		p->next = s; //插入點後前驅的後繼指向
	}
}

DataType DLinkListDelete(DLinkList * &L, int i)//刪除第i個位置上的值,並返回其值
{
	DLinkList *p = L, *s;
	int j = 0;

	if (L == NULL)
	{
		cout << "Function DLinkListDelete" << "刪除出錯" << endl;
		return 0;
	}

	while (j < i - 1 && p != NULL)
	{
		j++;
		p = p->next;
	}

	if (p == NULL)
	{
		return 0;
	}
	else
	{
		s = p->next;
		if (s == NULL)
		{
			return 0;
		}
		DataType e = p->Elem;
		p->next = s->next;
		if (p->next != NULL)
		{
			p->next->prior = p;
		}
		free(s);
		return e;
	}
}

2.4、迴圈連結串列

3、佇列

3.1、順序佇列

#pragma once
#ifndef SQQUEUE_H
#define SQQUEUE_H

#define MaxSize 50
typedef int DataType;

typedef struct SQueue //建立一個結構體,裡面包含陣列和隊頭和隊尾
{
	DataType data[MaxSize]; 
	int front, rear; //front表示隊頭,rear表示隊尾,入隊頭不動尾動,出隊尾不動頭動
}SqQueue;

void SqQueueInit(SqQueue *&Q);              //佇列初始化
void SqQueueClear(SqQueue *$Q);             //清空佇列
bool isSqQueueEmpty(SqQueue *Q);            //判斷佇列長度
int  SqQueueLength(SqQueue *Q);             //求佇列的長度
void SqQueueDisplay(SqQueue *Q);            //輸出佇列
void EnSqQueue(SqQueue *& Q,DataType e);    //進隊
DataType DeSqQueue(SqQueue *& Q);           //出隊

#endif
#include<iostream>
#include"SqQueue.h"
using namespace std;

void SqQueueInit(SqQueue *&Q)   //佇列初始化
{
	Q = (SqQueue *)malloc(sizeof(Q));
	Q->front = Q->rear = 0;
}

void SqQueueClear(SqQueue *&Q)  //清空佇列
{
	free(Q); //對於順序棧,直接釋放記憶體即可
}

bool isSqQueueEmpty(SqQueue *Q) //判斷佇列長度
{
	return (Q->front == Q->rear);
}

int  SqQueueLength(SqQueue *Q)  //求佇列的長度
{
	return Q->rear - Q->front;  //此處有問題
}

void EnSqQueue(SqQueue *& Q,DataType e)    //進隊
{
	if (Q == NULL)
	{
		cout << "分配記憶體失敗!" << endl;
		return;
	}

	if (Q->rear >= MaxSize)  //入隊前進行隊滿判斷
	{
		cout << "The Queue is Full!" << endl;
		return;
	}

	Q->rear++;
	Q->data[Q->rear] = e;
}

DataType DeSqQueue(SqQueue *& Q)     //出棧
{
	if (Q == NULL)
	{
		return 0;
	}

	if (Q->front == Q->rear) //出隊前進行空隊判斷
	{
		cout << "This is an Empty Queue!" << endl;
		return 0;
	}

	Q->front--;
	return Q->data[Q->front];
}

void SqQueueDisplay(SqQueue *Q)           //輸出佇列
{
	if (Q == NULL)
	{
		return;
	}

	if (Q->front == Q->rear)
	{
		return;
	}
	int i = Q->front + 1;
	while (i <= Q->rear)
	{
		cout << Q->data[i] << "  ";
		i++;
	}
}

3.2、鏈式佇列

#pragma once
#ifndef LINKQUEUE_H
#define LINKQUEUE_H

typedef int DataType;

/*
  佇列的鏈式儲存中,由於需要指標分別指向
  隊頭和隊尾,因此造成了鏈隊節點與資料節點不同
  鏈隊節點:包含兩個指向隊頭隊尾的指標
  資料節點:一個指向下一個資料節點的指標和資料
*/


//定義資料節點結構體
typedef struct qnode
{
	DataType Elem;
	struct qnode *next;
}QDataNode;

//定義鏈隊節點結構體
typedef struct 
{
	QDataNode *front;
	QDataNode *rear;
}LinkQueue;

void LinkQueueInit(LinkQueue *&LQ);           //初始化鏈隊
void LinkQueueClear(LinkQueue *&LQ);          //清空鏈隊
bool isLinkQueueEmpty(LinkQueue *LQ);         //判斷鏈隊是否為空
int LinkQueueLength(LinkQueue *LQ);           //求鏈隊長度
bool EnLinkQueue(LinkQueue *&LQ,DataType e);  //進隊
DataType DeLinkQueue(LinkQueue *&LQ);         //出隊

#endif
#include<iostream>
#include"LinkQueue.h"
using namespace std;

void LinkQueueInit(LinkQueue *&LQ)   //初始化鏈隊
{
	LQ = (LinkQueue*)malloc(sizeof(LQ));
	LQ->front = LQ->rear = NULL;
}

void LinkQueueClear(LinkQueue *&LQ)  //清空鏈隊,清空佇列第一步:銷燬資料節點
                                    // 第二步:銷燬鏈隊節點
{
	QDataNode *p = LQ->front, *r;
	if (p != NULL)
	{
		r = p->next;
		while (r != NULL)
		{
			free(p);
			p = r;
			r = p->next;
		}
	}
	free(LQ);
}

bool isLinkQueueEmpty(LinkQueue *LQ) //判斷鏈隊是否為空
{
	return LQ->rear == NULL;  //1:非空;0:空
}

int LinkQueueLength(LinkQueue *LQ)  //求鏈隊長度
{
	QDataNode *p = LQ->front;
	int i = 0;
	while (p != NULL)
	{
		i++;
		p = p->next;
	}
	return i;
}
bool EnLinkQueue(LinkQueue *&LQ, DataType e)      //進隊
{
	QDataNode *p;

	if (LQ == NULL)
	{
		return 0;
	}

	p = (QDataNode*)malloc(sizeof(QDataNode));
	p->Elem = e;
	p->next = NULL; //尾插法

	if (LQ->front == NULL)//如果佇列中還沒有資料時
	{
		LQ->front = LQ->rear = p; //p為隊頭也為隊尾
	}
	else
	{
		LQ->rear->next = p;
		LQ->rear = p;
	}
}
DataType DeLinkQueue(LinkQueue *&LQ)  //出隊
{
	QDataNode *p;
	DataType e;
	if (LQ->rear == NULL)
	{
		cout << "This is an Empty queue!" << endl;
		return 0;
	}

	if (LQ->front == LQ->rear)
	{
		p = LQ->front;
		LQ->rear = LQ->front = NULL;
	}
	else
	{
		p = LQ->front;
		LQ->front = p->next;
		e = p->Elem;
	}
	free(p);
	return e;
}

4、棧

4.1、順序棧

#pragma once
#ifndef SQSTACK_H
#define SQSTACK_H

#define MaxSize 50//根據實際情況設定大小
typedef int DataType;

//順序棧也是一種特殊的順序表,建立一個
//結構體,裡面包含一個數組,儲存資料

//順序棧其實是將陣列進行結構體包裝

typedef struct Stack
{
	DataType Elem[MaxSize];
	int top;        //棧指標
}SqStack;

void SqStackInit(SqStack *&S);  //初始化棧
void SqStackClear(SqStack *&S);   //清空棧
int  SqStackLength(SqStack *S);  //求棧的長度
bool isSqStackEmpty(SqStack *S); //判斷棧是否為空
void SqStackDisplay(SqStack *S); //輸出棧元素
bool SqStackPush(SqStack *&S, DataType e);//元素e進棧
DataType SqStackPop(SqStack *&S);//出棧一個元素
DataType SqStackGetPop(SqStack *S);//取棧頂元素
#endif
#include<iostream>
#include"SqStack.h"
using namespace std;

void SqStackInit(SqStack *&S)  //初始化棧
{
	S = (SqStack*)malloc(sizeof(SqStack)); //開闢記憶體,建立棧
	S->top = -1;                           
}

void SqStackClear(SqStack *&S)   //清空棧
{
	free(S);
}

int  SqStackLength(SqStack *S)  //求棧的長度
{
	return S->top + 1;
}

bool isSqStackEmpty(SqStack *S) //判斷棧是否為空
{
	return (S->top == -1);
}

void SqStackDisplay(SqStack *S) //輸出棧元素
{
	for (int i = S->top; i > -1; i--)
	{
		cout << S->Elem[i] << "  ";
	}
}

bool SqStackPush(SqStack *&S, DataType e)//元素e進棧
{
	if ( S->top == MaxSize - 1)
	{
		cout << "The Stack Full!" << endl; //滿棧判斷
		return 0;
	}
	S->top++;
	S->Elem[S->top] = e;
	return 1;
}

DataType SqStackPop(SqStack *&S)//出棧一個元素
{
	DataType e;

	if(S->top== -1)  //空棧判斷
	{
		cout << "The Stack is Empty!" << endl;
		return 0;
	}

	e = S->Elem[S->top];//出棧元素儲存
	S->top--;
	return e;
}

DataType SqStackGetPop(SqStack *S)//取棧頂元素
{
	if (S->top == -1) //空棧判斷
	{
		cout << "The Stack is Empty" << endl;
		return 0;
	}

	return S->Elem[S->top];
}

4.2、鏈式棧

#pragma once
#ifndef LINKSTACK_H
#define LINKSTACK_H

typedef int DataType;

typedef struct LinkNode  //鏈式棧的結點定義和連結串列的結點定義是一樣的
{
	DataType Elem;                           //資料域
	struct LinkNode *next;                   //指標域
}LinkStack;

void LinkStackInit(LinkStack *& S);          //初始化列表
void LinkStackClear(LinkStack*&S);           //清空棧
int  LinkStackLength(LinkStack * S);         //求連結串列的長度
bool isLinkStackEmpty(LinkStack *S);         //判斷連結串列是否為空
bool LinkStackPush(LinkStack *S, DataType e);//元素e進棧
DataType LinkStackPop(LinkStack *S);         //出棧
DataType LinkStackGetPop(LinkStack *S);      //輸出棧頂元素
void LinkStackDisplay(LinkStack *S);         //從上到下輸出棧所有元素
#endif
#include<iostream>
#include"LinkStack.h"
using namespace std;

void LinkStackInit(LinkStack *& S) //初始化列表
{
	S = (LinkStack *)malloc(sizeof(LinkStack)); //分配記憶體
	S->next = NULL;
}

void LinkStackClear(LinkStack*&S) //清空棧
{
	LinkStack *p = S,*q = S->next;

	if (S == NULL)
	{
		return;
	}

	while (p != NULL)  //注意:與書中有點不同,定義兩個節點,一個當前節點,一個下一個節點
	{
		free(p);
		p = q;
		q = p->next;
	}
}

int  LinkStackLength(LinkStack * S)//求連結串列的長度
{
	int i = 0;
	LinkStack *p = S->next; //跳過頭結點
	while (p != NULL)
	{
		i++;
		p = p->next;
	}
	return i;
}

bool isLinkStackEmpty(LinkStack *S)//判斷連結串列是否為空
{
	return S->next == NULL; //1:空;0:非空
}

bool LinkStackPush(LinkStack *S, DataType e)//元素e進棧
{
	LinkStack *p;
	p = (LinkStack*)malloc(sizeof(LinkStack)); //建立結點
	if (p == NULL)
	{
		return 0;
	}

	p->Elem = e;  //將元素賦值
	p->next = S->next; //將新建結點的p->next指向原來的棧頂元素
	S->next = p; //將現在棧的起始點指向新建結點

	return 1;
}

DataType LinkStackPop(LinkStack *S)//出棧
{
	LinkStack *p;
	DataType e;
	if (S->next == NULL)
	{
		cout << "The Stack is Empty!" << endl;
		return 0;
	}
	p = S->next; //跳過頭結點
	e = p->Elem;
	S->next = p->next;
	return e;
}

DataType LinkStackGetPop(LinkStack *S)//輸出棧頂元素
{
	if (S->next == NULL)
	{
		cout << "The Stack is Empty!" << endl;
		return 0;
	}
	return S->next->Elem;  //頭結點
}

void LinkStackDisplay(LinkStack *S)//從上到下輸出棧所有元素
{
	LinkStack *p = S->next;
	while(p != NULL)
	{
		cout << p->Elem << "  ";
		p = p->next;
	}
	cout << endl;
}

5、二叉樹

5.1、二叉樹的鏈式儲存

#pragma once
#ifndef LINKBTREE_H
#define LINKBTREE_H

#define MaxSize 100      //樹的深度
typedef char DataType;

typedef struct BTNode    //定義一個二叉樹節點
{
	DataType Elem;
	BTNode *Lchild;
	BTNode *Rchild;
}LinkBTree;

void LinkBTreeCreate(LinkBTree *& BT, char *str);//有str建立二叉鏈
LinkBTree* LinkBTreeFindNode(LinkBTree * BT, DataType e); //返回e的指標
LinkBTree *LinkBTreeLchild(LinkBTree *p);//返回*p節點的左孩子節點指標
LinkBTree* LinkBTreeRight(LinkBTree *p);//返回*p節點的右孩子節點指標
int LinkBTreeDepth(LinkBTree *BT);//求二叉鏈的深度
void LinkBTreeDisplay(LinkBTree * BT);//以括號法輸出二叉鏈
int LinkBTreeWidth(LinkBTree *BT);//求二叉鏈的寬度
int LinkBTreeNodes(LinkBTree * BT);//求節點個數
int LinkBTreeLeafNodes(LinkBTree *BT);//求二叉鏈的葉子節點個數

void LinkBTreeProOeder(LinkBTree *BT); //前序遞迴遍歷
void LinkBTreeProOederRecursion(LinkBTree *BT);//前序非遞迴遍歷
void LinkBTreeInOeder(LinkBTree *BT);//中序遞迴遍歷
void LinkBTreeInOederRecursion(LinkBTree *BT);//中序非遞迴遍歷
void LinkBTreePostOeder(LinkBTree *BT);//後序遞迴遍歷
void LinkBTreePostOederRecursion(LinkBTree *BT);//後序非遞迴遍歷

#endif
#include<iostream>
#include"LinkBTree.h"
using namespace std;

void LinkBTreeCreate(LinkBTree *& BT, char *str)//有str建立二叉鏈
{
	LinkBTree *St[MaxSize], *p = NULL;
	int top = -1, k, j = 0;

	char ch;
	BT = NULL;
	ch = str[j];

	while (ch != '\0')
	{
		switch (ch)
		{
		case '(':top++; St[top] = p; k = 1; break;//為左節點,top表示層數,k表示左右節點,碰到一個'('二叉樹加一層,碰到一個',',變成右子樹
		case ')':top--; break;
		case ',':k = 2; break; //為右節點
		default: p = (LinkBTree *)malloc(sizeof(LinkBTree));
			p->Elem = ch;
			p->Lchild = p->Rchild = NULL;
			if (BT == NULL)
			{
				BT = p;   //根節點
			}
			else
			{
				switch (k)
				{
				case 1:St[top]->Lchild = p; break;
				case 2:St[top]->Rchild = p; break;
				}
			}
		}
		j++;
		ch = str[j];
	}
}

LinkBTree *LinkBTreeFindNode(LinkBTree * BT, DataType e) //返回元素e的指標
{
	LinkBTree *p;

	if (BT == NULL)
	{
		return NULL;
	}
	else if (BT->Elem == e)
	{
		return BT;
	}
	else
	{
		p = LinkBTreeFindNode(BT->Lchild, e); //遞迴
		if (p != NULL)
		{
			return p;
		}
		else
		{
			return LinkBTreeFindNode(BT->Lchild, e);
		}
	}
}

LinkBTree *LinkBTreeLchild(LinkBTree *p)//返回*p節點的左孩子節點指標
{
	return p->Lchild;
}

LinkBTree *LinkBTreeRight(LinkBTree *p)//返回*p節點的右孩子節點指標{
{
	return p->Rchild;
}

int LinkBTreeDepth(LinkBTree *BT)//求二叉鏈的深度
{
	int LchildDep, RchildDep;
	if (BT == NULL)
	{
		return 0;
	}
	else
	{
		LchildDep = LinkBTreeDepth(BT->Lchild);
		RchildDep = LinkBTreeDepth(BT->Rchild);
	}
	return (LchildDep > RchildDep) ? (LchildDep + 1) : (RchildDep + 1);
}

void LinkBTreeDisplay(LinkBTree * BT)//以括號法輸出二叉鏈
{
	if (BT != NULL)
	{
		cout << BT->Elem;
		if (BT->Lchild != NULL || BT->Rchild != NULL)
		{
			cout << '(';
			LinkBTreeDisplay(BT->Lchild);
			if (BT->Rchild != NULL)
			{
				cout << ',';
			}
			LinkBTreeDisplay(BT->Rchild);
			cout << ')';
			
		}
	}
}

int LinkBTreeWidth(LinkBTree *BT)//求二叉鏈的寬度
{
	return 0;
}

int LinkBTreeNodes(LinkBTree * BT)//求節點個數
{
	if (BT == NULL)
	{
		return 0;
	}
	else if (BT->Lchild == NULL && BT->Rchild == NULL)   //為葉子節點的情況
	{
		return 1;
	}
	else
	{
		return (LinkBTreeNodes(BT->Lchild) + LinkBTreeNodes(BT->Rchild) + 1);
	}
}

int LinkBTreeLeafNodes(LinkBTree *BT)//求二叉鏈的葉子節點個數
{
	if (BT == NULL)
	{
		return 0;
	}
	else if (BT->Lchild == NULL && BT->Rchild == NULL)  //為葉子節點的情況
	{
		return 1;
	}
	else
	{
		return (LinkBTreeLeafNodes(BT->Lchild) + LinkBTreeLeafNodes(BT->Rchild));
	}
}

void LinkBTreeProOeder(LinkBTree *BT) //前序非遞迴遍歷
{
	LinkBTree *St[MaxSize], *p;
	int top = -1;
	if (BT != NULL)
	{
		top++;
		St[top] = BT;     //將第一層指向根節點

		while (top > -1)
		{
			p = St[top]; //第一層
			top--;       //退棧並訪問該節點
			cout << p->Elem << " ";

			if (p->Rchild != NULL)
			{
				top++;
				St[top] = p->Rchild;
			}

			if (p->Lchild != NULL)
			{
				top++;
				St[top] = p->Lchild;
			}
		}
		cout << endl;
	}
}

void LinkBTreeProOederRecursion(LinkBTree *BT)//前序遞迴遍歷
{
	if (BT != NULL)
	{
		cout << BT->Elem<<" ";
		LinkBTreeProOeder(BT->Lchild);
		LinkBTreeProOeder(BT->Rchild);
	}
}

void LinkBTreeInOeder(LinkBTree *BT)//中序非遞迴遍歷
{
	LinkBTree *St[MaxSize], *p;
	int top = -1;
	if (BT != NULL)
	{
		p = BT;
		while (top > -1 || p != NULL)
		{
			while (p != NULL)
			{
				top++;
				St[top] = p;
				p = p->Lchild;
			}

			if (top> -1)
			{
				p = St[top];
				top--;
				cout << p->Elem <<" ";
				p = p->Rchild;
			}
		}
		cout << endl;
	}
}

void LinkBTreeInOederRecursion(LinkBTree *BT)//中序遞迴遍歷
{
	if (BT != NULL)
	{
		LinkBTreeProOeder(BT->Lchild);
		cout << BT->Elem << " ";
		LinkBTreeProOeder(BT->Rchild);
	}
}

void LinkBTreePostOeder(LinkBTree *BT)//後序非遞迴遍歷
{
	LinkBTree *St[MaxSize], *p;
	int top = -1,flag;
	if (BT != NULL)
	{
		do
		{
			while (BT != NULL)
			{
				top++;
				St[top] = BT;
				BT = BT->Lchild;
			}
			
			p = NULL;
			flag = 1;
			while (top != -1 && flag)
			{
				BT = St[top];
				if (BT->Rchild == p)
				{
					cout << BT->Elem << " ";
					top--;
					p = BT;
				}
				else
				{
					BT = BT->Lchild;
					flag = 0;
				}
			}
		} while (top != -1);
		
		cout << endl;
	}
}

void LinkBTreePostOederRecursion(LinkBTree *BT)//後序遞迴遍歷
{
	if (BT != NULL)
	{
		LinkBTreeProOeder(BT->Lchild);
		LinkBTreeProOeder(BT->Rchild);
		cout << BT->Elem << " ";
	}
}

5.2、哈夫曼樹

        在許多應用上,常常將樹中的節點附上一個有著某種意義的數值,稱此數值為該節點的權,從樹根節點到該節點的路徑長度與該節點權值之積稱為帶權路徑長度。樹中所有葉子節點的帶權路徑長度之和稱為該樹的帶權路徑長度,如下:

WPL=\sum Wi*Li,其中共有n個葉子節點的數目,Wi表示葉子節點i的權值,Li表示根節點到葉子節點的路徑長度。

       在n個帶有權值結點構成的二叉樹中,帶權路徑長度WPL最小的二叉樹稱為哈夫曼樹。又稱最優二叉樹。

哈夫曼樹演算法:

(1)根據給定的n個權值,使對應節點構成n顆二叉樹的森林T,其中每顆二叉樹中都只有一個帶權值的Wi的根節點,其左右節點均為空

(2)在森林中選取兩顆根節點權值最小的子樹分別作為左、右子樹構造一顆新二叉樹,且置新的二叉樹的根節點的權值為其左、右子樹上根節點的權值之和。

(3)在森林中,用新得到的二叉樹代替選取的兩棵樹

(4)重複(2)和(3),直到T只含一棵樹為止

定理:對於具有n個葉子節點的哈夫曼樹,共有2n-1個節點

程式碼如下:

#pragma once

typedef double Wi;  //假設權值為雙精度

struct HTNode       //每一個節點的結構內容,
{
	Wi weight;      //節點的權值
	HTNode *left;   //左子樹
	HTNode *right;  //右子樹
};

void PrintHuffman(HTNode * HuffmanTree);  //輸出哈夫曼樹
HTNode * CreateHuffman(Wi a[], int n);    //建立哈夫曼樹
#include"Huffman.h"
#include<iostream>

/*
  哈夫曼演算法:
  (1)根據給定的n個權值建立n個二叉樹的森林,其中n個二叉樹的左右子樹均為空
  (2)在森林中選擇權值最小的兩個為左右子樹構造一顆新樹,根節點
       為權值最小的之和
  (3)在森林中,用新的樹代替選取的兩棵樹
  (4)重複(2)和(3)
  定理:n個葉子節點的哈夫曼樹共有2n-1個節點
*/

/*
       a[]    I    存放的是葉子節點的權值
       n      I    葉子節點個數
  return      O    返回一棵哈夫曼樹
*/
HTNode* CreateHuffman(Wi a[], int n)    //建立哈夫曼樹
{
	int i, j;
	HTNode **Tree, *HuffmanTree; //根據n個權值宣告n個二叉樹的森林,二級指標表示森林(二叉樹的集合)
	Tree = (HTNode**)malloc(n * sizeof(HTNode));  //代表n個葉節點,為n棵樹分配記憶體空間
	HuffmanTree = (HTNode*)malloc(sizeof(HTNode));
	//實現第一步:建立n棵二叉樹,左右子樹為空
	for (i = 0; i < n; i++) 
	{
		Tree[i] = (HTNode*)malloc(sizeof(HTNode));
		Tree[i]->weight = a[i];
		Tree[i]->left = Tree[i]->right = nullptr;
	}

	//第四步:重複第二和第三步
	for (i = 1; i < n; i++)   //z這裡表示第i次排序
	{
		//第二步:假設權值最小的根節點二叉樹下標為第一個和第二個
		//打擂臺選擇最小的兩個根節點樹
		int k1 = 0, k2 = 1;  		
		for (j = k2; j < n; j++) 
		{
			if (Tree[j] != NULL)
			{
				if (Tree[j]->weight < Tree[k1]->weight) //表示j比k1和k2的權值還小,因此兩個值都需要更新
				{
					k2 = k1;         
					k1 = j;
				}
				else if(Tree[j]->weight < Tree[k2]->weight) //k1 < j < k2,需要更新k2即可
				{
					k2 = j;
				}
			}
		}

		//第三步:一次選擇結束後,將更新一顆樹
		HuffmanTree = (HTNode*)malloc(sizeof(HTNode));              //每次一輪結束,建立一個根節點
		HuffmanTree->weight = Tree[k1]->weight + Tree[k2]->weight;  //更新後的根節點權值為左右子樹權值之和
		HuffmanTree->left = Tree[k1];  //最小值點為左子樹
		HuffmanTree->right = Tree[k2]; //第二小點為右子樹

		Tree[k1] = HuffmanTree;
		Tree[k2] = nullptr;
	}
	free(Tree);
	return HuffmanTree;
}

//先序遍歷哈夫曼樹
void PrintHuffman(HTNode * HuffmanTree)  //輸出哈夫曼樹
{
	if (HuffmanTree == nullptr)
	{
		return;
	}
	std::cout << HuffmanTree->weight;
	if (HuffmanTree->left != nullptr || HuffmanTree->right != nullptr)
	{
		std::cout << "(";
		PrintHuffman(HuffmanTree->left);
		if (HuffmanTree->right != nullptr)
		{
			std::cout << ",";
		}
		PrintHuffman(HuffmanTree->right);
		std::cout << ")";
	}
}

6、查詢演算法

6.1、線性表查詢(順序查詢、折半查詢)

順序查詢

#include<iostream>
using namespace std;

#define Max 100

typedef int KeyType;
typedef char InfoType[10];
typedef struct
{
	KeyType key;  //表示位置
	InfoType data; //data是具有10個元素的char陣列
}NodeType;

typedef NodeType SeqList[Max]; //SeqList是具有Max個元素的結構體陣列

int SeqSearch(SeqList R, int n, KeyType k)
{
	int i = 0;

	while (i < n && R[i].key != k)
	{
		cout << R[i].key;//輸出查詢過的元素
		i++;
	}

	if (i >= n)
	{
		return -1;
	}
	else
	{
		cout << R[i].key << endl;
		return i;
	}
}


int main()
{
	SeqList R;
	int n = 10;
	KeyType k = 5;
	int a[] = { 3,6,8,4,5,6,7,2,3,10 },i;
	for (int i = 0; i < n; i++)
	{
		R[i].key = a[i];
	}
	cout << endl;
	if ((i = SeqSearch(R, n, k)) != -1)
		cout << "元素" << k << "的位置是" << i << endl;
	system("pause");
	return 0;
}

折半查詢

#include<iostream>
using namespace std;

#define Max 100
typedef int KeyType;
typedef char InfoType[10];

typedef struct
{
	KeyType key;
	InfoType data;
}NodeType;

typedef NodeType SeqList[Max];

int BinSeqList(SeqList R, int n, KeyType k)
{
	int low = 0, high = n - 1, mid, cout = 0;

	while (low <= high)
	{
		mid = (low + high) / 2;
		//cout << "第" << ++cout << "次查詢:" << "在" << "[" << low << "," << high << "]" << "中查詢到元素:" << R[mid].key << endl;

		if (R[mid].key == k)
		{
			return mid;
		}
		if (R[mid].key > k)
			high = mid - 1;
		else
			low = mid + 1;
	}
}

int main()
{
	SeqList R;
	KeyType k = 9;
	int a[] = { 1,2,3,4,5,6,7,8,9,10 },i,n = 10;
	for (i = 0; i < n; i++)
	{
		R[i].key = a[i];
	}
	cout << endl;
	if ((i = BinSeqList(R, n, k)) != -1)
	{
		cout << "元素" << k << "的位置是:" << i << endl;
	}
	system("pause");
	return 0;
}

6.2、樹表查詢(二叉排序樹、平衡二叉樹、B-樹、B+樹)

二叉排序樹(B樹)

#pragma once
#ifndef BSTREE_H
#define BSTREE_H

#define Max 100
typedef int KeyType;
typedef char InfoType[10];

typedef struct node
{
	KeyType key;    //關鍵字項
	InfoType data;  //其他資料項
	struct node *Lchild, *Rchild;
}BSTNode;


BSTNode *BSTreeCreat(KeyType A[], int n);         //由陣列A(含有n個關鍵字)中的關鍵字建立一個二叉排序樹
int BSTreeInsert(BSTNode *& BST, KeyType k); //在以*BST為根節點的二叉排序樹中插入一個關鍵字為k的結點
int BSTreeDelete(BSTNode *& BST, KeyType k); //在bst中刪除關鍵字為k的結點
void BSTreeDisplay(BSTNode * BST);            //以括號法輸出二叉排序樹
int BSTreeJudge(BSTNode * BST);              //判斷BST是否為二叉排序樹

#endif
#include<iostream>
#include"BSTree.h"

using namespace std;

BSTNode *BSTreeCreat(KeyType A[], int n)         //由陣列A(含有n個關鍵字)中的關鍵字建立一個二叉排序樹
{
	BSTNode *BST = NULL;
	int i = 0;
	while (i < n)
	{
		if(BSTreeInsert(BST,A[i]) == 1)
		{
			cout << "第" << i + 1 << "步,插入" << A[i] << endl;
			BSTreeDisplay(BST);
			cout << endl;
			i++;
		}
	}
	return BST;
}

int BSTreeInsert(BSTNode *& BST, KeyType k) //在以*BST為根節點的二叉排序樹中插入一個關鍵字為k的結點
{
	if (BST == NULL)
	{
		BST = (BSTNode *)malloc(sizeof(BSTNode));
		BST->key = k;
		BST->Lchild = BST->Rchild = NULL;
		return 1;
	}
	else if(k == BST->key)
	{
		return 0;
	}
	else if(k > BST->key)
	{
		return BSTreeInsert(BST->Rchild, k);
	}
	else
	{
		return BSTreeInsert(BST->Lchild, k);
	}
}

int BSTreeDelete(BSTNode *& BST, KeyType k) //在bst中刪除關鍵字為k的結點
{
	if (BST == NULL)
	{
		return 0;
	}
	else
	{
		if (k < BST->key)
		{
			return BSTreeDelete(BST->Lchild, k);
		}
		else if (k>BST->key)
		{
			return BSTreeDelete(BST->Rchild, k);
		}
		else
		{
			Delete(BST)
		}
	}
}

void BSTreeDisplay(BSTNode * BST)            //以括號法輸出二叉排序樹
{
	if (BST != NULL)
	{
		cout << BST->key;
		if (BST->Lchild != NULL || BST->Rchild != NULL)
		{
			cout << '(';
			BSTreeDisplay(BST->Lchild);
			if (BST->Rchild != NULL)
			{
				cout << ',';
			}
			BSTreeDisplay(BST->Rchild);
			cout << ')';
		}
	}
}

KeyType predt = -32767;

int BSTreeJudge(BSTNode * BST)              //判斷BST是否為二叉排序樹
{
	int b1, b2;

	if (BST == NULL)
	{
		return 1;
	}
	else
	{
		b1 = BSTreeJudge(BST->Lchild);
		if (b1 == 0 || predt >= BST->key)
		{
			return 0;
		}
		predt = BST->key;
		b2 = BSTreeJudge(BST->Rchild);
		return b2;
	}
}

void Delete(BSTNode*& p)                   //刪除二叉排序樹*p節點
{
	BSTNode* q;

	if (p->Rchild == nullptr)             //當刪除的節點沒有右子樹,只有左子樹時,根據二叉樹的特點,
	{                                     //直接將左子樹根節點放在被刪節點的位置。
		q = p;
		p = p->Lchild;
		free(p);
	}
	else if (p->Lchild == nullptr)       //當刪除的結點沒有左子樹,只有右子樹時,根據二叉樹的特點,
	{                                    //直接將右子樹結點放在被刪結點位置。
		q = p;
		p = p->Rchild;
		free(p);
	}
	else
	{
		Delete1(p, p->Lchild);         //當被刪除結點有左、右子樹時
	}


}

void Delete1(BSTNode* p, BSTNode* &r)      //當刪除的二叉排序樹*P節點有左右子樹的刪除過程
{
	BSTNode *q;
	if (p->Lchild != nullptr)
	{
		Delete1(p, p->Rchild);           //遞迴尋找最右下節點
	}                                    //找到了最右下節點*r
	else                                 //將*r的關鍵字賦值個*p
	{
		p->key = r->key;
		q = r;
		r = r->Lchild;
		free(q);
	}
}

平衡二叉樹:若一棵二叉樹中的每個節點的左右子樹高度至多相差1,則稱此二叉樹為平衡二叉樹。其中平衡因子的定義為:平衡二叉樹中每個節點有一個平衡因子,每個節點的平衡因子是該節點左子樹高度減去右子樹的高度,若每個平衡因子的取值為0,-1,1則該樹為平衡二叉樹。

B-樹

用作外部查詢的資料結構,其中的資料存放在外存中,是一種多路搜尋樹

1、所有的葉子節點放在同一層,並且不帶資訊

2、樹中每個節點至多有m棵子樹

3、若根節點不是終端節點,則根節點至少有兩棵子樹

4、除根節點外的非葉子節點至少有m/2棵子樹

5、每個節點至少存放m/2-1個至多m-1個關鍵字

6、非葉子節點的關鍵字數=指向兒子指標的個數-1

7、非葉子節點的關鍵字依次遞增

8、非葉子節點指標:P[1],P[2],...P[m];其中P[i]指向關鍵字小於K[1]的子樹,P[i]指向關鍵字屬於(K[i-1],K[i])的子樹

6.3、雜湊表查詢

        從根本上說,一個雜湊表包含一個數組,通過特殊的索引值(鍵)來訪問陣列中的元素。

        雜湊表的主要思想是通過一個雜湊函式,在所有可能的鍵與槽位之間建立一張對映表。雜湊函式每次接收一個鍵將返回與鍵對應的雜湊編碼或者雜湊值。鍵的資料型別可能多種多樣,但雜湊值只能是整型。

       計算雜湊值和在陣列中進行索引都只消耗固定的時間,因此雜湊表的最大亮點在於它是一種執行時間在常量級的檢索方法。當雜湊函式能夠保證不同的鍵生成的雜湊值互不相同時,就說雜湊值能直接定址想要的結果。

       散列表(Hash table,也叫雜湊表),是根據關鍵碼值(Key value)而直接進行訪問的資料結構。也就是說,它通過把關鍵碼值對映到表中一個位置來訪問記錄,以加快查詢的速度。這個對映函式叫做雜湊函式,存放記錄的陣列叫做散列表

       給定表M,存在函式f(key),對任意給定的關鍵字值key,代入函式後若能得到包含該關鍵字的記錄在表中的地址,則稱表M為雜湊(Hash)表,函式f(key)為雜湊(Hash) 函式。

雜湊函式能使對一個數據序列的訪問過程更加迅速有效,通過雜湊函式,資料元素將被更快地定位。

實際工作中需視不同的情況採用不同的雜湊函式,通常考慮的因素有:

· 計算雜湊函式所需時間

· 關鍵字的長度

· 雜湊表的大小

· 關鍵字的分佈情況

· 記錄的查詢頻率

1. 直接定址法:取關鍵字或關鍵字的某個線性函式值為雜湊地址。即H(key)=key或H(key) = a·key + b,其中a和b為常數(這種雜湊函式叫做自身函式)。若其中H(key)中已經有值了,就往下一個找,直到H(key)中沒有值了,就放進去。這種雜湊函式計算簡單,並且不可能有衝突產生,當關鍵字連續時,可用直接定址法;否則關鍵字的不連續將造成記憶體單元的大量浪費。

2. 數字分析法:分析一組資料,比如一組員工的出生年月日,這時我們發現出生年月日的前幾位數字大體相同,這樣的話,出現衝突的機率就會很大,但是我們發現年月日的後幾位表示月份和具體日期的數字差別很大,如果用後面的數字來構成雜湊地址,則衝突的機率會明顯降低。因此數字分析法就是找出數字的規律,儘可能利用這些資料來構造衝突機率較低的雜湊地址。

3. 平方取中法:當無法確定關鍵字中哪幾位分佈較均勻時,可以先求出關鍵字的平方值,然後按需要取平方值的中間幾位作為雜湊地址。這是因為:平方後中間幾位和關鍵字中每一位都相關,故不同關鍵字會以較高的概率產生不同的雜湊地址。

4、除留取餘法:用關鍵字k除以某個不大於雜湊表長度m的數p,將所得的餘數作為雜湊地址的方法。h(k) = k mod p,其中p取不大於m的素數最佳

#pragma once
#define MaxSize 20  //此處表示雜湊表長度m

#define NULLKEY -1  //表示該節點為空節點,未存放資料
#define DELEKEY -2  //表示該節點資料被刪除,

typedef int Key;   //關鍵字型別

typedef struct
{
	Key key;  //關鍵字值
	int count;  //探查次數
}HashTable[MaxSize];

void HTInsert(HashTable HT,int &n,Key k,int p); //將關鍵字插入雜湊表中
void HTCreate(HashTable HT, Key x[], int n, int m, int p); //建立雜湊表
int  HTSearch(HashTable HT, int p, Key k);      //在雜湊表中查詢關鍵字
int  HTDelete(HashTable HT, int p, Key k, int &n); //刪除雜湊表中關鍵字k
void HTDisplay(HashTable HT,int n,int m);
#include"HT.h"
#include<iostream>

/*解決衝突用開地址的線性探查法*/
void HTInsert(HashTable HT, int &n, Key k, int p) //將關鍵字k插入雜湊表中
{
	int i,addr;  //i:記錄探查數;adddr:記錄雜湊表下標
	addr = k % p;
	if (HT[addr].key == NULLKEY || HT[addr].key == DELEKEY) //表示該出為空,可以儲存值
	{
		HT[addr].key = k;
		HT[addr].count = 1;
	}
	else  //表示存在雜湊衝突
	{
		i = 1;
		do
		{
			addr = (addr + 1) % p;   //雜湊衝突解決辦法:開地址法中的線性探查法,從當前衝突地址開始依次往後排查
			i++;
		} while (HT[addr].key != NULLKEY || HT[addr].key != DELEKEY);
	}
	n++;//表示插入一個元素後雜湊表共儲存的元素數量
}

/*
  HT      I/O     雜湊表
  x[]      I      關鍵字陣列
  n        I      關鍵字個數
  m        I      雜湊表長度
  p        I      為小於m的數p,取不大於m的素數最好
*/
void HTCreate(HashTable HT, Key x[], int n, int m, int p)//建立雜湊表
{
	for (int i = 0; i < m; i++)  //建立一個空的雜湊表
	{
		HT[i].key = NULLKEY;
		HT[i].count = 0;
	}

	int n1 = 0;
	for (int i = 0; i < n; i++)
	{
		HTInsert(HT, n1, x[i], p);
	}
}

int  HTSearch(HashTable HT, int p, Key k)      //在雜湊表中查詢關鍵字
{
	int addr; //用來儲存關鍵字k在雜湊表中的下標
	addr = k % p;

	while(HT[addr].key != NULLKEY || HT[addr].key != k)
	{
		addr = (addr + 1) % p;  //存在著雜湊衝突
	}
	if (HT[addr].key == k)
		return addr;
	else
		return -1;
}

/*
  注:刪除並非真正的刪除,而是標記
*/
int  HTDelete(HashTable HT, int p, Key k, int &n)//刪除雜湊表中關鍵字k
{
	int addr;
	addr = HTSearch(HT, p, k);
	if (addr != -1)
	{
		HT[addr].key = DELEKEY;
		n--;
		return 1;
	}
	else
	{
		return 0;
	}
}

void HTDisplay(HashTable HT, int n, int m)   //輸出雜湊表
{
	std::cout << "  下標:";
	for (int i = 0; i < m; i++)
	{
		std::cout<< i << "  ";
	}
	std::cout << std::endl;

	std::cout << " 關鍵字:";
	for (int i = 0; i < m; i++)
	{
		std::cout << HT[i].key << "  ";
	}
	std::cout << std::endl;

	std::cout << "探查次數:";
	for (int i = 0; i < m; i++)
	{
		std::cout << HT[i].key << "  ";
	}
	std::cout << std::endl;
}

7、排序演算法

7.1、直接插入排序

//按遞增順序進行直接插入排序
/*
  假設待排序的元素存放在陣列R[0...n-1]中,排序過程中的某一時刻
  R被劃分為兩個子區間R[0..i-1]和R[i..n-1],其中,前一個子區間
  是已排好序的有序區間,後一個則是未排序。直接插入排序的一趟操
  作是將當前無序區間的開頭元素R[i]插入到有序區間R[0..i-1]中適當
  的位置中,使R[0..i]變成新的區間
*/
void InsertSort(RecType R[], int n)
{
	int i, j, k;
	RecType temp;

	for (i = 1; i < n; i++)
	{
		temp = R[i];  //
		j = i - 1;

		while (j>=0 && temp.key <R[j].key)  //如果無序區間值比有序區間小,有序區間值往後挪
		{
			R[j + 1] = R[j];
			j--;
		}

		R[j + 1] = temp;
		cout << "i:" << i << " ";

		for (k = 0; k < n; k++)
		{
			cout << R[k].key << " ";
		}
		cout << endl;
	}
}

7.2、折半插入排序

7.3、希爾排序

//希爾排序
/* 
   先取定一個小於n的整數d1作為第一個增量,
   把表的全部元素分成d1個組,所有相互之間
   距離為d1的倍數的元素放在同一個組中,在
   各組內進行直接插入排序;然後,取第二個
   增量d2,重複上述的分組過程和排序過程,
   直至所取的增量dt=1,即所有元素放在同一
   組中進行直接插入排序
*/
void ShellInsert(RecType R[], int n)
{
	int i, j, d,k;
	RecType temp;
	d = n / 2;
	while (d > 0)
	{
		for (i = d; i < n; i++)
		{
			j = i - d;
			while (j >= 0 && R[j].key < R[j+d].key)
			{
				temp = R[j];
				R[j] = R[j + d];
				R[j + d] = temp;
				j = j - d;
			}
		}

		cout << d<<"  ";
		for (k = 0; k < n; k++)
		{
			cout << R[k].key << " ";
		}
		cout << endl;
		d = d / 2; //減少增量
	}
}