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; //減少增量
	}
}

7.4、冒泡排序

/****************************************
* function          冒泡排序             *
* param     a[]     待排序的數組          *
* param     n       數組長度              *
* return            無                   *
* good time         O(n)                *
* avg time          O(n^2)              *
* bad time          O(n^2)              *
* space             O(1)                *
* stable            yes                 *
*****************************************/
void BubbleSort(int a[],int n)
{
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
        {
            if (a[i] < a[j])
                swap(a[i], a[j]);
        }
    }
}

7.5、快速排序

void Quick_Sort(int a[], int left, int right)
{
    if (left < right)
    {
        //1.隨機取基準值,然後交換到left那裏
//      srand(GetTickCount());
//      int m = (rand() % (right - left)) + left;
        //2.取前中後的中值,然後交換到left那裏
//      int m = Mid(left, (left + right / 2), right);
//      swap(a[m], a[left]);
        int midIndex = Partition(a, left, right); //獲取新的基準keyindex
        Quick_Sort(a, left, midIndex - 1);  //左半部分排序
        Quick_Sort(a, midIndex + 1, right); //右半部分排序
    }
}

void QuickSort(int a[], int n)
{
    Quick_Sort(a, 0, n - 1);
}

7.6、直接選擇排序

/****************************************
* function          選擇排序法           *
* param     a[]     待排序的數組          *
* param     n       數組長度                *
* return            無                   *
* good time         O(n^2)              *
* avg time          O(n^2)              *
* bad time          O(n^2)              *
* space             O(1)                *
* stable            no                  *
*****************************************/
void SelectSort(int a[], int n)
{
    int min = 0;
    for (int i = 0; i < n; i++)
    {
        min = i;
        for (int j = i + 1; j < n; j++)
        {
            if (a[j] < a[min])
                min = j;
        }
        if (min != i)
            swap(a[min], a[i]);
    }
}

7.7、堆排序

#include<iostream>
using namespace std;

#define Max 20
typedef int KeyType;
typedef struct
{
	KeyType key;
}RecType;

void HeapDisplay(RecType R[], int i, int n) //括號法輸出堆
{
	if (i < n)
	{
		cout << R[i].key << " ";
	}

	if (2 * i <= n || 2 * i + 1 < n)
	{
		cout << "(";

		if (2 * i <= n)
		{
			HeapDisplay(R, 2 * i, n);
		}

		cout << ",";

		if (2 * i + 1 <= n)
		{
			HeapDisplay(R, 2 * i + 1, n);
		}

		cout << ")";
	}
}

void HeapSift(RecType R[], int low, int high) //調整堆
{
	int i = low, j = 2 * i; //R[j]是R[i]的左孩子

	RecType temp = R[i];

	while (j <= high)
	{
		if (j < high && R[j].key < R[j + 1].key)
		{
			j++;
		}

		if (temp.key < R[j].key)
		{
			R[i] = R[j];
			i = j;
			j = 2 * i;
		}
		else break;
	}
	R[i] = temp;
}


/* 堆排序:在排序過程中,將R[1..n]
   看成是一顆順序存儲的完全二叉樹,
   利用完全二叉樹中雙親節點和孩子節
   點之間的內在關係,在當前無序區中
   選擇關鍵字最小或最大的元素
*/
void HeapSort(RecType R[], int n)
{
	int i;
	RecType temp;
	for (i = n / 2; i >= 1; i--)
	{
		HeapSift(R, i, n);
	}
	cout << "初始堆爲:";
	HeapDisplay(R,1,n);  //輸出初始堆
	cout << endl;

	for (i = n; i >= 2; i--)
	{
		temp = R[1];
		R[1] = R[i];
		R[i] = temp;
		HeapSift(R, 1, i - 1); //刷選R[1]結點,得到i-1個結點的堆
		cout << "篩選調整得到的堆:";
		HeapDisplay(R, 1, i - 1);
	}
}

int main()
{
	int i, k, n = 10;
	KeyType a[] = { 6,8,9,7,0,1,3,2,4,5 };

	RecType R[Max];
	for (i = 1; i <= n; i++)
	{
		R[i].key = a[i - 1];
	}
	cout << endl;
	cout << "  初始關鍵字:";
	for (k = 1; k <= n; k++)
	{
		cout << R[k].key << " ";
	}
	cout << endl;

	for (i = n / 2; i >= 1; i--)
	{
		HeapSift(R, i, n);
	}
	HeapSort(R, n);

	cout << "  最終結果:";
	for (k = 1; k <= n; k++)
	{
		cout << R[k].key << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

7.8、歸併排序

7.9、基數排序

8、圖

8.1、圖的基本概念

       在圖形結構中,每一個元素可以有零個和多個前驅,也可以有零個和多個後繼,也就是說,元素之間的關係是任意的。無論多麼複雜的圖都是由頂點和邊構成。

路徑長度:指一條路徑上經過的邊的數目

連通:圖中任意兩點都連通,則爲連通圖,否則爲非連通圖

強連通:有向圖中,圖中任意兩點都連通,則爲強連通圖

權:圖中每一條邊都可以附有一個數值,這種與邊相關的數值稱爲權

頂點的度:在無向圖中,某頂點具有的邊數爲該頂點的度。在有向圖中又分爲入度和出度,以頂點i爲終點的入邊的數目,稱爲入度,以頂點i爲起點的出邊的數目,稱爲該頂點的出度,二者之和爲該頂點的度。

圖的存儲結構:鄰接矩陣

圖的鄰接矩陣的表示方式需要一個二維數組來表示。

優點:直觀、容易理解,容易判斷出任意兩個頂點是否有邊,容易計算各個頂點的度。

缺點:如果是完全圖時,鄰接矩陣是最好的方法,但是對於稀疏矩陣,由於它邊較少,但是頂點多,這樣就會造成空間浪費。

圖的存儲結構:鄰接表

鄰接表是圖的一種鏈式存儲結構。

8.2、圖的遍歷

       深度優先遍歷方法(DFS):從圖中某個初始定點出發,首先訪問初始點,然後選擇一個與頂點相鄰且沒有被訪問的頂點w爲初始頂點,在繼續從頂點w出發進行深度優先遍歷,直到所有人頂點被訪問。顯然該遍歷過程是一個遞歸過程。(一次訪問一個點)

        廣度優先遍歷方法(BFS):首先訪問初始點v,接着訪問頂點v的所有未被訪問過的所有鄰接點v1,v2,v3,,,vt,訪問每一個頂點的所有未被訪問過的鄰接點,依次類推。(一次訪問所有鄰接點)

8.3、生成樹和最小生成樹

        現在假設有一個很實際的問題:我們要在n個城市中建立一個通信網絡,則連通這n個城市需要佈置n-1一條通信線路,這個時候我們需要考慮如何在成本最低的情況下建立這個通信網? 
       於是我們就可以引入連通圖來解決我們遇到的問題,n個城市就是圖上的n個頂點,然後,邊表示兩個城市的通信線路,每條邊上的權重就是我們搭建這條線路所需要的成本,所以現在我們有n個頂點的連通網可以建立不同的生成樹,每一顆生成樹都可以作爲一個通信網,當我們構造這個連通網所花的成本最小時,搭建該連通網的生成樹,就稱爲最小生成樹。

普里姆(Prim)算法:
       首先就是從圖中的一個起點a開始,把a加入U集合,然後,尋找從與a有關聯的邊中,權重最小的那條邊並且該邊的終點b在頂點集合:(V-U)中,我們也把b加入到集合U中,並且輸出邊(a,b)的信息,這樣我們的集合U就有:{a,b},然後,我們尋找與a關聯和b關聯的邊中,權重最小的那條邊並且該邊的終點在集合:(V-U)中,我們把c加入到集合U中,並且輸出對應的那條邊的信息,這樣我們的集合U就有:{a,b,c}這三個元素了,一次類推,直到所有頂點都加入到了集合U。該算法的核心是一次加入一個點,然後添加該點邊權值最小的那條,直到所有點都添加進去。

下面我們對下面這幅圖求其最小生成樹:

這裏寫圖片描述

假設我們從頂點v1開始,所以我們可以發現(v1,v3)邊的權重最小,所以第一個輸出的邊就是:v1—v3=1: 
這裏寫圖片描述

然後,我們要從v1和v3作爲起點的邊中尋找權重最小的邊,首先了(v1,v3)已經訪問過了,所以我們從其他邊中尋找,發現(v3,v6)這條邊最小,所以輸出邊就是:v3—-v6=4 
這裏寫圖片描述

然後,我們要從v1、v3、v6這三個點相關聯的邊中尋找一條權重最小的邊,我們可以發現邊(v6,v4)權重最小,所以輸出邊就是:v6—-v4=2. 
這裏寫圖片描述

然後,我們就從v1、v3、v6、v4這四個頂點相關聯的邊中尋找權重最小的邊,發現邊(v3,v2)的權重最小,所以輸出邊:v3—–v2=5 
這裏寫圖片描述

然後,我們就從v1、v3、v6、v4,v2這2五個頂點相關聯的邊中尋找權重最小的邊,發現邊(v2,v5)的權重最小,所以輸出邊:v2—–v5=3 
這裏寫圖片描述

最後,我們發現六個點都已經加入到集合U了,我們的最小生成樹建立完成。

代碼實現:

#include<iostream>
#include<string>
#define MAXSIZE 100

using namespace std;

struct M_g  //定義鄰接矩陣
{
	string *Head;  //頂點表
	int  **arc;              //鄰接矩陣可以看成邊表
	int  N_v, N_e;           //記錄頂點數和邊數
};

//創建一個鄰接矩陣表示圖
void OnCreateM_g(M_g *&x)
{
	int i, j, k, w;
	cout << "輸入頂點個數和邊數" << endl;
	cin >> x->N_v >> x->N_e; //依次輸入頂點數和邊數
	cout << "頂點數爲:" << x->N_v << ";" << "邊數爲:" << x->N_e << endl;

	for (int i = 0; i < x->N_v; i++)
	{
		cin >> x->Head[i];  //依次輸入頂點
	}

	for (int i = 0; i < x->N_v; i++)  //給出每個頂點是否連接
	{
		for (int j = 0; j < x->N_v; j++)
		{
			x->arc[i][j] = INT_MAX;
		}
	}

	cout << "輸入(Vi,Vj)的上標i,下標j和權w" << endl;
	for (k = 0; k < x->N_e; k++)  //
	{
		cin >> i >> j >> w;
		x->arc[i][j] = w;
		x->arc[j][i] = x->arc[i][j]; //無向圖是對稱矩陣
	}
}

//打印圖
void Print(M_g x)
{
	int i;
	for (i = 0; i < x.N_v; i++)
	{
		for (int j = 0; j < x.N_v; j++)
		{
			if (x.arc[i][j] == INT_MAX)
			{
				cout << "∞" << "  ";
			}
			else
			{
				cout << x.arc[i][j] << "  ";
			}
		}
		cout << endl;
	}
}


//記錄邊的信息,這些邊都是達到end的所有邊中,權重最小的那個
struct Assis_array
{
	int start; //邊的起點
	int end;   //邊的終點
	int weight;//邊的權重
};


//Prim算法實現,圖的存儲方式爲鄰接矩陣
void Prim(M_g *x, int begin)
{
	//創建一個保存到達某個頂點的各個邊中權重最大的那個邊的結構體數組
	Assis_array *edge = new Assis_array[x->N_v];

	int j;

	//edge初始化,初始化時用頂點0到該點的邊爲權值最大邊
	for (j = 0; j < x->N_v; j++)
	{
		if (j != begin - 1)
		{
			edge[j].start = begin - 1;
			edge[j].end = j;
			edge[j].weight = x->arc[begin - 1][j];
		}
	}

	//將起點的edge的權值設置爲-1,表示已經加入到集合U了
	edge[begin - 1].weight = -1;

	//b訪問剩下的頂點,並依次加入到集合U
	for (j = 1; j < x->N_v; j++)
	{
		int min = INT_MAX;
		int k;
		int index;

		//尋找數組權重最小的那邊條邊
		for (k = 0; k < x->N_v; k++)
		{
			if (edge[k].weight != -1)
			{
				if (edge[k].weight < min)
				{
					min = edge[k].weight;
					index = k;
				}
			}
		}

		//將權重最小的那條邊的終點也加入集合U
		edge[index].weight = -1;

		//輸出對應的邊的信息
		cout << edge[index].start
			<< "-----"
			<< edge[index].end
			<< "="
			<< x->arc[edge[index].start][edge[index].end] << endl;

		//更新我們的edge數組
		for (k = 0; k < x->N_v; k++)
		{
			if (x->arc[edge[index].end][k] < edge[k].weight)
			{
				edge[k].weight = x->arc[edge[index].end][k];
				edge[k].start  = edge[index].end;
				edge[k].end    = k;
			}
		}
	}
}


int main() {
	M_g *p;
	p = (M_g*)malloc(sizeof(M_g));
	OnCreateM_g(p);
	Prim(p,1);
	system("pause");
	return 0;

}

克魯斯卡(Kruskal)算法:

(1)將圖中的所有邊都去掉。

(2)將邊按照權值從小到大的順序添加到圖中,保證添加的過程中不會形成環

(3)重複以上一步策略直到連接所有頂點,此時就生成了最小生成樹。這是一種貪心策略。

模擬克魯斯卡算法生成最小生成樹的詳細的過程:

首先完整的圖如下圖: 
這裏寫圖片描述

然後,我們需要從這些邊中找出權重最小的那條邊,可以發現邊(v1,v3)這條邊的權重是最小的,所以我們輸出邊:v1—-v3=1 
這裏寫圖片描述

然後,我們需要在剩餘的邊中,再次尋找一條權重最小的邊,可以發現邊(v4,v6)這條邊的權重最小,所以輸出邊:v4—v6=2 
這裏寫圖片描述

然後,我們再次從剩餘邊中尋找權重最小的邊,發現邊(v2,v5)的權重最小,所以可以輸出邊:v2—-v5=3, 
這裏寫圖片描述

然後,我們使用同樣的方式找出了權重最小的邊:(v3,v6),所以我們輸出邊:v3—-v6=4 
這裏寫圖片描述

好了,現在我們還需要找出最後一條邊就可以構造出一顆最小生成樹,但是這個時候我們有三個選擇:(v1,V4),(v2,v3),(v3,v4),這三條邊的權重都是5,首先我們如果選(v1,v4)的話,得到的圖如下: 
這裏寫圖片描述 
我們發現,這肯定是不符合我們算法要求的,因爲它出現了一個環,所以我們再使用第二個(v2,v3)試試,得到圖形如下: 
這裏寫圖片描述

我們發現,這個圖中沒有環出現,而且把所有的頂點都加入到了這顆樹上了,所以(v2,v3)就是我們所需要的邊,所以最後一個輸出的邊就是:v2—-v3=5,最後,我們的最小生成樹完成。

代碼實現:

8.4、最短路徑

        最短路徑:從一個頂點到另一個頂點可能存在多條路徑,每條路徑上所經過的邊數可能不同,即路徑長度不同,把路徑長度最短的那條路徑叫做最短路徑。對於帶權的圖,應該考慮路徑上各邊的權值,通常把一條路徑上所經過的邊的權值之和定爲該路徑的長度或者帶權路徑長度。

1、從一個頂點到其餘各個頂點的最短路徑:狄克斯特拉(Dijkstra)算法

        Dijkstra算法是典型最短路徑算法,用於計算一個節點到其他節點的最短路徑。 
        它的主要特點是以起始點爲中心向外層層擴展(廣度優先搜索思想(BFS)),直到擴展到終點爲止。

基本思想

        通過Dijkstra計算圖G中的最短路徑時,需要指定起點s(即從頂點s開始計算)。

        此外,引進兩個集合S和U。S的作用是記錄已求出最短路徑的頂點(以及相應的最短路徑長度),而U則是記錄還未求出最短路徑的頂點(以及該頂點到起點s的距離)。 初始時,S中只有起點s;U中是除s之外的頂點,並且U中頂點的路徑是"起點s到該頂點的路徑"。然後,從U中找出路徑最短的頂點,並將其加入到S中;接着,更新U中的頂點和頂點對應的路徑。 然後,再從U中找出路徑最短的頂點,並將其加入到S中;接着,更新U中的頂點和頂點對應的路徑。... 重複該操作,直到遍歷完所有頂點。
操作步驟

(1) 初始時,S只包含起點s;U包含除s外的其他頂點,且U中頂點的距離爲"起點s到該頂點的距離"[例如,U中頂點v的距離爲(s,v)的長度,然後s和v不相鄰,則v的距離爲∞]。

(2) 從U中選出"距離最短的頂點k",並將頂點k加入到S中;同時,從U中移除頂點k。

(3) 更新U中各個頂點到起點s的距離。之所以更新U中頂點的距離,是由於上一步中確定了k是求出最短路徑的頂點,從而可以利用k來更新其它頂點的距離;例如,(s,v)的距離可能大於(s,k)+(k,v)的距離。

(4) 重複步驟(2)和(3),直到遍歷完所有頂點。

狄克斯特拉算法圖解

以上圖G4爲例,來對狄克斯特拉進行算法演示(以第4個頂點D爲起點)。

初始狀態:S是已計算出最短路徑的頂點集合,U是未計算除最短路徑的頂點的集合! 
第1步:將頂點D加入到S中。 此時,S={D(0)}, U={A(∞),B(∞),C(3),E(4),F(∞),G(∞)}。     注:C(3)表示C到起點D的距離是3。

第2步:將頂點C加入到S中。 上一步操作之後,U中頂點C到起點D的距離最短;因此,將C加入到S中,同時更新U中頂點的距離。以頂點F爲例,之前F到D的距離爲∞;但是將C加入到S之後,F到D的距離爲9=(F,C)+(C,D)。此時,S={D(0),C(3)}, U={A(∞),B(23),E(4),F(9),G(∞)}。

第3步:將頂點E加入到S中。上一步操作之後,U中頂點E到起點D的距離最短;因此,將E加入到S中,同時更新U中頂點的距離。還是以頂點F爲例,之前F到D的距離爲9;但是將E加入到S之後,F到D的距離爲6=(F,E)+(E,D)。此時,S={D(0),C(3),E(4)}, U={A(∞),B(23),F(6),G(12)}。

第4步:將頂點F加入到S中。此時,S={D(0),C(3),E(4),F(6)}, U={A(22),B(13),G(12)}。

第5步:將頂點G加入到S中。此時,S={D(0),C(3),E(4),F(6),G(12)}, U={A(22),B(13)}。

第6步:將頂點B加入到S中。此時,S={D(0),C(3),E(4),F(6),G(12),B(13)}, U={A(22)}。

第7步:將頂點A加入到S中。此時,S={D(0),C(3),E(4),F(6),G(12),B(13),A(22)}。

此時,起點D到各個頂點的最短距離就計算出來了:A(22) B(13) C(3) D(0) E(4) F(6) G(12)

代碼實現:

#include<iostream>

using namespace std;


//用鄰接矩陣來表示圖
struct M_g
{
	char *vexs;    //頂點集合
	int  vexnum;   //頂點個數
	int  edgnum;   //邊的條數
	int  **matrix; //鄰接矩陣
};

//邊的結構體
struct edge
{
	char start;  //邊的起點
	char end;    //邊的終點
	int  weight; //邊的權重
};

//創建一個鄰接矩陣表示圖
void OnCreateM_g(M_g *&x)
{
	int i, j, k, w;
	cout << "輸入頂點個數和邊數" << endl;
	cin >>  x->vexnum >> x->edgnum; //依次輸入頂點數和邊數
	cout << "頂點數爲:" << x->vexnum << ";" << "邊數爲:" << x->edgnum << endl;

	for (int i = 0; i < x->vexnum; i++)
	{
		cin >> x->vexs[i];  //依次輸入頂點
	}

	for (int i = 0; i < x->vexnum; i++)  //給出每個頂點是否連接
	{
		for (int j = 0; j < x->vexnum; j++)
		{
			x->matrix[i][j] = INT_MAX;
		}
	}

	cout << "輸入(Vi,Vj)的上標i,下標j和權w" << endl;
	for (k = 0; k < x->edgnum; k++)  //
	{
		cin >> i >> j >> w;
		x->matrix[i][j] = w;
		x->matrix[j][i] = x->matrix[i][j]; //無向圖是對稱矩陣
	}
}

//打印圖
void Print(M_g x)
{
	int i;
	for (i = 0; i < x.vexnum; i++)
	{
		for (int j = 0; j < x.vexnum; j++)
		{
			if (x.matrix[i][j] == INT_MAX)
			{
				cout << "∞" << "  ";
			}
			else
			{
				cout << x.matrix[i][j] << "  ";
			}
		}
		cout << endl;
	}
}

//Dijkstra算法
void Dijkstra(M_g *x, int StartNode)
{
	int *dist, int *prev;  //dist表示頂點StartNode到頂點i的最短路徑長度,prev頂點StartNode到頂點i的最短路徑的前一個節點
	int i,j, k;
	int min;
	int *flag;  //表示頂點StartNode到頂點i的最短路徑是否成功獲取

	//初始化
	for (int i = 0; i < x->vexnum; i++)
	{
		flag[i] = 0;
		prev[i] = StartNode;
		dist[i] = x->matrix[StartNode][i];
	}

	//對頂點StartNode自身初始化
	flag[StartNode] = 0;
	dist[StartNode] = 0;

	//遍歷x->vecnum-1次,每次找出一個頂點的最短路徑
	for (int i = 1; i < x->vexnum; i++)
	{
		min = INT_MAX;
		for (int j = 0; j < x->vexnum; j++)
		{
			if (flag[j] == 0 && dist[j] < min)
			{
				min = dist[j];
				k = j;
			}
		}
		flag[k] = 1;//表示k已經是最短路徑

		//更新未獲取最短路徑的頂點的最短路徑和前驅頂點
		for (int  j = 0; j < x->vexnum; j++)
		{
			if (flag == 0)
			{
				if (x->matrix[k][j] <INT_MAX && dist[k] + x->matrix[k][j] <dist[j])
				{
					dist[j] = dist[k] + x->matrix[k][j];
					prev[j] = k;
				}
			}
		}
	}
}

9、串的模式匹配

9.1、BF算法

        算法的基本思想:從主串的第1個字符起和模式串的第一個字符比較,若相等,則繼續逐個比較後續字符,否則從主串的第2字符起重新和模式串的字符比較。依次類推,直到模式串t中的每個字符依次和主串s中的一個連續的字符序列相等,則匹配成功。否則匹配不成功。

#include<stdio.h>
//串的定長順序存儲表示
#define MAXSTRLEN 50  // // 用戶可在50以內定義最大串長
typedef unsigned char SString[MAXSTRLEN + 1];//0號單元存放串的長度
 
//返回子串T在主串S中第pos個字符之後的位置。若不存在,則函數值爲0。其中,T非空,1<=pos<=StrLength(S)。
int indexBF(SString S, SString T, int pos){
	int i = pos, j = 1;
	while(i <= S[0] && j <= T[0]){
		if(S[i] == T[j]){
			i++;
			j++;
		}else{
			i = i - j + 2;//i回到原位置是i - j + 1 ,所以i退到遠位置的下一個位置是i - j + 1 + 1
			j = 1;
		}
	}
	if(j > T[0]){//如果j > len(T),說明模式串T與S中某子串完全匹配
		return i - T[0];//因爲i是已經自增過一次了,所以是i-len(T)而不是i-len(T)+1
	}else
		return 0;
}
 
void init(SString &S, char str[]){
	int i = 0;
	while(str[i]!='\0'){
		S[i+1] = str[i];
		i++;
	}
	S[i+1] = '\0';
	S[0] = i;
}
 
void printStr(SString Str){
	for(int i = 1; i <= Str[0]; i++){
		printf("%c", Str[i]);
	}
	printf("\n");
}
void main(){
	SString S ;
	init(S, "ababcabcacbab");
	printStr(S);
 
	SString T;
	init(T, "abcac");
	printStr(T);
 
	int index = indexBF(S, T, 1);
	printf("index is %d\n", index);
}

9.2、KMP算法

         樸素算法會順序遍歷,比較第一次的時候p[0]處失配,然後向後移動繼續匹配。數據量大的時候這麼做肯定是不可行的。所以這裏就會有KMP算法!在一次失配之後,KMP算法認爲這裏已經失配了,就不能在比較一遍了,而是將字符串P向前移動(已匹配長度-最大公共長度)位,接着繼續比較下一個位置。這裏已匹配長度好理解,但是最大公共長度是什麼吶?這裏就出現了next數組,next數組:next[i]表示的是P[0-i]最大公共前後綴公共長度。這裏肯定又有人要問了,next數組這麼奇葩的定義,爲什麼就能算出來字符串需要向後平移幾位纔不會重複比較吶?

        上圖中紅星標記爲例,此時在p[4]處失配,已匹配長度爲4,而next[3]=2(也就是babaa中前後綴最大公共長度爲0),這時候向後平移已匹配長度-最大公共長度=2位,P[0]到達原來的P[2]的位置,如果只平移一位,P[0]到達p[1]的位置這個位置沒有匹配這次操作就是無用功所以浪費掉了時間。已知前綴後綴中的最大公共長度,下次位移的時候直接把前綴位移到後綴上面直接產生匹配,這樣直接從後綴的後一位開始比較就可以了。這樣將一下無意義的位移過濾掉剩去了不少的時間。

void makeNext(const char P[],int next[])
{
    int q,k;
    int m=strlen(P);
    next[0]=0;
    for (q=1,k=0;q<m;++q)
    {
        while(k>0&&P[q]!=P[k])
            k = next[k-1];
        /*
        這裏的while循環很不好理解!
        就是用一個循環來求出前後綴最大公共長度;
        首先比較P[q]和P[K]是否相等如果相等的話說明已經K的數值就是已匹配到的長的;
        如果不相等的話,那麼next[k-1]與P[q]的長度,爲什麼吶?因爲當前長度不合適
        了,不能增長模板鏈,就縮小看看next[k-1]
        的長度能夠不能和P[q]匹配,這麼一直遞歸下去直到找到
        */
        if(P[q]==P[k])//如果當前位置也能匹配上,那麼長度可以+1
        {
            k++;
        }
        next[q]=k;
    }
}
int kmp(const char T[],const char P[],int next[])
{
    int n,m;
    int i,q;
    n = strlen(T);
    m = strlen(P);
    makeNext(P,next);
    for (i=0,q=0;i<n;++i)
    {
        while(q>0&&P[q]!= T[i])
            q = next[q-1];
        /*
        這裏的循環就是位移之後P的前幾個字符能個T模板匹配
        */
        if(P[q]==T[i])
        {
            q++;
        }
        if(q==m)//如果能匹配的長度剛好是T的長度那麼就是找到了一個能匹配成功的位置
        {
            printf("Pattern occurs with shift:%d\n",(i-m+1));
        }
    }
}

10、分治算法

11、動態規劃算法

12、回溯法

 

 

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