1. 程式人生 > 實用技巧 >資料結構課程設計

資料結構課程設計

資料結構課程設計

首先恭喜你已經通過明亮的眼睛找到了這篇部落格的密碼,這篇文章是給那些“忙於學習”的同學寫的。所以,廢話不多說,直接上程式碼。

一、報數問題

問題描述

有n個小朋友圍成一圈玩遊戲,小朋友從1至n編號,2號小朋友坐在1號小朋友的順時針方向,3號小朋友坐在2號小朋友的順時針方向,……,1號小朋友坐在n號小朋友的順時針方向。
 遊戲開始,從1號小朋友開始順時針報數,接下來每個小朋友的報數是上一個小朋友報的數加1。若一個小朋友報的數為k的倍數或其末位數(即數的個位)為k,則該小朋友被淘汰出局,不再參加以後的報數。當遊戲中只剩下一個小朋友時,該小朋友獲勝。
 例如,當n=5, k=2時:
 1號小朋友報數1;
 2號小朋友報數2淘汰;
 3號小朋友報數3;
 4號小朋友報數4淘汰;
 5號小朋友報數5;
 1號小朋友報數6淘汰;
 3號小朋友報數7;
 5號小朋友報數8淘汰;
 3號小朋友獲勝。
 給定n和k,請問最後獲勝的小朋友編號為多少?

輸入格式

  輸入一行,包括兩個整數n和k,意義如題目所述。

輸出格式

  輸出一行,包含一個整數,表示獲勝的小朋友編號。

樣例輸入

5 2

樣例輸出

3

樣例輸入

7 3

樣例輸出

4

資料規模和約定

對於所有評測用例,1 ≤ n ≤ 1000,1 ≤ k ≤ 9。
要求:利用單向迴圈連結串列儲存結構模擬此過程。

程式碼部分

#include<iostream>
using namespace std;
#include<stdlib.h>
//這題是模擬過程可抽象為在每個時間片狀態模擬。
//像是動畫製作,每一幀代表一片時間,每一幀的圖案對應狀態,幀與幀的轉換等價於狀態改變
typedef struct person{
 int index;//個人屬性:序號+存活狀態
 struct person *next;
}Person;

typedef Person* Ptr;//方便同時定義多個結構體指標

Ptr create_single_circle_list(int n)//建立單向迴圈連結串列,構造初始狀態及個體間的關係
{
 //Person* head;Person* pre;Person* last;
 //結構體指標必須分開定義,或者把Person* 定義成Ptr
 Ptr head,pre,last;//頭、後繼、前驅指標

 head =(Ptr)malloc(sizeof(Person));
 head->index=1;
 pre=head;
 for(int i=1;i<n;i++)
 {
     Person* last=(Ptr)malloc(sizeof(Person));
     last->index=i+1;
     pre->next=last;
     pre=last;
 }
 pre->next=head;
 return head;
}

Ptr play(Ptr head,int n,int k)//報數過程模擬
{
 Ptr pre,last;
 int count=1;//模擬報號數字
 pre=head;
 while(n>1)//全域性屬性:n為目前還存活的人數
 {
     //index既代表每個人固定的序號,
     //也代表著當前個人存活狀態:正數=>存活; -1=>死亡
     //只有活著的人可以報數,所以只對存活者操作
     if(pre->index!=-1)
     {
         //若當前存活者報的數為k的倍數或個位數為k,
         //當前狀態變為死亡
         //當前存活人數減一
         //計算或是模擬過程無非是狀態的轉變,此題的結果判斷依賴於當前存活人數,
         //分解為個人存活、全域性存活人數兩個狀態
         if(count%k==0||count%10==k)
         {
             pre->index=-1;
             n--;
         }

         count++;//只要當前狀態為存活,就必須報數
     }
     pre=pre->next;//無論當前存活狀態如何都必須模擬報數人的移動
 }

 return head;
}

void output(Ptr head,int n)//輸出獲勝編號
{
 Ptr pre=head;
 while(n>1)
 {
     if(pre->index!=-1)
     {
         cout<<pre->index;
         break;
     }
     else pre=pre->next;
 }
}

int main()
{
 int n,k;
 cin>>n>>k;
 Ptr head=create_single_circle_list(n);
 head=play(head,n,k);
 output(head,n);
 return 0;
}

二、迷宮問題求解

問題描述

任務

​ 可以輸入一個任意大小的迷宮資料,用非遞迴的方法求出一條走出迷宮的路徑,並將路徑輸出;

要求

在上交資料中請寫明:儲存結構、基本演算法(可以使用程式流程圖)、源程式、測試資料和結果、演算法的時間複雜度、另外可以提出演算法的改進方法;

程式碼部分

#include <bits/stdc++.h>
using namespace std;
#define MAXSIZE 1000
typedef int Status;
typedef struct
{
	int x;
	int y;
} Postype;
typedef struct
{
	int ord;	  //通道塊路徑上的序號
	Postype seat; //通道快在迷宮中的座標位置
	int dir;	  //從此通道快走向下一個通道快的方向
} SElemType;	  //棧的元素型別
typedef struct
{
	//SElemType data[MAXSIZE];
	SElemType *top;
	SElemType *base;
} Stack; //棧的結構型別
typedef struct
{
	char arr[MAXSIZE][MAXSIZE];
} MAZETYPE; //迷宮結構體

MAZETYPE maze;
//建立一個迷宮圖
void InitMaze(int m, int n)
{
	printf("請開始您的私人地圖\n");
	for (int i = 1; i <= m; i++)
	{
		for (int j = 1; j <= n; j++)
		{
			cin >> maze.arr[i][j];
		}
	}
	printf("你建立的迷宮為(最外圈為牆)...\n");
	for (int i = 0; i <= m + 1; i++) //加一圈圍牆
	{
		maze.arr[i][0] =  '1';
		maze.arr[i][n + 1] = '1';
	}
	for (int j = 0; j <= n + 1; j++)
	{
		maze.arr[0][j] = '1';
		maze.arr[m + 1][j] = '1';
	}
	for (int i = 0; i <= m + 1; i++) //輸出迷宮
	{
		for (int j = 0; j <= n; j++)
		{
			printf("%c ", maze.arr[i][j]);
		}
		printf("%c", maze.arr[i][n + 1]);
		printf("\n");
	}
}
//建立一個棧初始化
Status initStack(Stack &s)
{
	s.base = (SElemType *)malloc(MAXSIZE * sizeof(SElemType));
	if (!s.base)
		return 0;
	s.top = s.base;
	return 1;
}
//入棧
void Push(Stack &s, SElemType e)
{
	*s.top++ = e;
}
//出棧
void Pop(Stack &s, SElemType &e)
{
	e = *--s.top;
}
//判棧空
Status StackEmpty(Stack &s)
{
	if (s.top == s.base)
		return 1;
	else
		return 0;
}
//可通過
Status Pass(Postype curpos)
{
	if (maze.arr[curpos.x][curpos.y] == '0')
		return 1;
	else
		return 0;
}
//留下通過的足跡
void Foot(Postype curpos)
{
	maze.arr[curpos.x][curpos.y] = '*';
}
//標記不可通的位置
void MarkPrint(Postype curpos)
{
	maze.arr[curpos.x][curpos.y] =  '!';
}
//判斷是否已經到達了出口
Status StructCmp(Postype a, Postype b)
{
	if (a.x == b.x && a.y == b.y)
		return 1;
	else
		return 0;
}
//尋找下一個位置
Postype NextPos(Postype CurPos, int Dir)
{
	Postype ReturnPos;
	switch (Dir)
	{
	case 1: //探索右
		ReturnPos.x = CurPos.x;
		ReturnPos.y = CurPos.y + 1;

		break;
	case 2: //下
		ReturnPos.x = CurPos.x + 1;
		ReturnPos.y = CurPos.y;

		break;
	case 3: //左
		ReturnPos.x = CurPos.x;
		ReturnPos.y = CurPos.y - 1;

		break;
	case 4: //上
		ReturnPos.x = CurPos.x - 1;
		ReturnPos.y = CurPos.y;
		break;
	default:
		break;
	}
	return ReturnPos;
}
//進行路徑的查詢
Status MazePath(Postype start, Postype end1, Stack &s1)
{
	/*
        如果迷宮maze中存在從入口start到出口end的通道,
        則求得一條存放在棧中(從棧底到棧頂),並返回1,否則返回0。
    */
	SElemType e;
	Postype curpos = start;
	int curstep = 1;
	do
	{
		if (Pass(curpos))
		{
			Foot(curpos);			  //留下足跡
			e = {curstep, curpos, 1}; //將序列號,可通點的座標和存入
			Push(s1, e);			  //將其存入戰中
			if (StructCmp(curpos, end1))
			{
				maze.arr[end1.x][end1.y] =  '@';
				return 1;
			}							 //若找到出口則輸出
			curpos = NextPos(curpos, 1); //查詢下一個位置
			curstep++;
		}
		else
		{
			if (!StackEmpty(s1))
			{
				SElemType e;
				Pop(s1, e);
				curstep--;
				while (e.dir == 4 && !StackEmpty(s1))
				{
					MarkPrint(e.seat);
					Pop(s1, e);
					curstep--; //若此位置的上下左右都不通,則標記並在棧中刪除此位置
				}
				if (e.dir < 4) //&& !StackEmpty(s))//與上面相對應,則繼續查詢此位置的下一個可通位置
				{
					e.dir++;
                    curpos = NextPos(e.seat, e.dir);
					curstep++;
					Push(s1, e);
				}
			}
		}
	} while (!StackEmpty(s1));
	return 0;
}
void mg(Postype s,Postype e,Stack s1,int m,int n)
{
    SElemType p;
    if (MazePath(s, e, s1))
	{
		printf("it is sucessful!\n");
		cout << "-=================================" << endl;
		for (int i = 0; i <= m + 1; i++)
		{
			for (int j = 0; j <= n + 1; j++)
			{
				printf("%c ", maze.arr[i][j]);
			}
			printf("\n");
		}
		while (!StackEmpty(s1))
		{
			Pop(s1, p);
			printf("(%d,%d)>>", p.seat.x, p.seat.y);
		}
	}
	else
		printf("it's a error\n");
}
int main()
{
	int m, n,l;
	Postype s, e;
	Stack s1;
	initStack(s1);
	cout << "請輸入這個迷宮圖的行號和列號:" << endl;
	cin >> m >> n;
	InitMaze(m, n);
	cout << "請輸入這個迷宮圖的入口和出口:" << endl;
	cin >> s.x >> s.y >> e.x >> e.y;
	mg(s,e,s1,m,n);
	/*if (MazePath(s, e, s1))
	{
		printf("it is sucessful!\n");
		cout << "-=================================" << endl;
		for (int i = 0; i <= m + 1; i++)
		{
			for (int j = 0; j <= n + 1; j++)
			{
				printf("%c ", maze.arr[i][j]);
			}
			printf("\n");
		}
		while (!StackEmpty(s1))
		{
			Pop(s1, p);
			printf("(%d,%d)>>", p.seat.x, p.seat.y);
		}
	}
	else
		printf("it's a error\n");
    */
	return 0;
}
/*
輸入:
8 8
    0 1 0 1 0 0 1 0
    0 0 1 0 0 1 0 1
    0 0 0 1 1 0 1 0
    0 1 0 0 0 1 0 1
    0 1 0 0 0 1 1 1
    0 1 0 0 1 0 1 0
    1 0 0 0 0 0 0 1
    0 1 0 0 0 1 0 0
輸出:
1 1
8 8
    (8,8)>>(8,7)>>(7,7)>>(7,6)>>(7,5)>>(7,4)>>(6,4)>>(5,4)>>(5,5)>>(4,5)>>(4,4)>>(4,3)>>(3,3)>>(3,2)>>(2,2)>>(2,1)>>(1,1)>>
*/

三、哈夫曼編/譯碼器

1、問題描述

任務

建立最優二叉樹函式。

要求

可以建立函式輸入二叉樹,並輸出其哈夫曼樹。

在上交資料中請寫明:儲存結構、基本演算法(可以使用程式流程圖)、輸入輸出、源程式、測試資料和結果、演算法的時間複雜度、另外可以提出演算法的改進方法;

利用哈夫曼編碼進行通訊可以大大提高通道利用率,縮簡訊息傳輸時間,降低傳輸成本。但是,這要求在傳送端通過一個編碼系統對待傳資料預先編碼,在接收端將傳來的資料進行譯碼(復原)。對於雙工通道(即可以雙向傳輸資訊的通道),每端都需要一個完整的編/譯碼系統。試為這樣的資訊收發站寫一個哈夫曼碼的編/譯碼系統。

一個完整的系統應具有以下功能:

  1. I:初始化(Initialization)。從終端讀入字符集大小n,以及n個字元和n個權值,建立哈夫曼樹,並將它存於檔案hfmTree中。
  2. E:編碼(Encoding)。利用已建好的哈夫曼樹(如不在記憶體,則從檔案hfmTree中讀入),對檔案ToBeTran中的正文進行編碼,然後將結果存入檔案CodeFile中。
  3. D:譯碼(Decoding)。利用已建好的哈夫曼樹將檔案CodeFile中的程式碼進行譯碼,結果存入檔案TextFile中。
  4. P:印程式碼檔案(Print)。將檔案CodeFile以緊湊格式顯示在終端上,每行50個程式碼。同時將此字元形式的編碼檔案寫入檔案CodePrin中。
  5. T:印哈夫曼樹(Tree printing)。將已在記憶體中的哈夫曼樹以直觀的方式(樹或凹入表形式)顯示在終端上,同時將此字元形式的哈夫曼樹寫入檔案TreePrint中。

2、程式碼部分

#include <iostream>
#include <fstream>
#include <cstring>
#define MAXSIZE 100
#include<bits/stdc++.h>
using namespace std;

//哈夫曼樹的儲存結構
typedef struct HaffmanTree{
	double weight;					//權值
	char letter;
	int parent,lChild,rChild;	//結點的雙親、左孩子、右孩子的下標
}HTNode,*HuffmanTree;			//態分配陣列儲存哈夫曼樹

typedef char **HuffmanCode;		//動態分配陣列儲存哈夫曼編碼表

//棧儲存結構
typedef struct{
	int *base;		//棧底指標
	int *top;		//棧頂指標
	int stackSize;	//棧的最大容量
}SqStack;

//棧初始化
int InitStack (SqStack &S){
	S.base = new int[MAXSIZE];  //為順序棧動態分配空間
	if(!S.base)
	    //分配失敗
		exit(0);
	S.top = S.base;
	S.stackSize = MAXSIZE;
	return 1;
}

//入棧
int Push(SqStack &S,int elem){
	if(S.top-S.base == S.stackSize)		//棧滿
		return 0;
	*S.top++ = elem;
	return 1;
}

//出棧
int Pop(SqStack &S,int &elem){
	if(S.top == S.base)				//棧空
		return 0;
	elem = *--S.top;				//取出棧頂元素並出棧
	return 1;
}

//判斷是否為空棧
int StackEmpty(SqStack &S){
	if(S.top == S.base)		//棧空,返回1
		return 1;
	else
		return 0;
}

//尋找最小的兩個元素           s1<s2
void Select(HuffmanTree &HT,int n,int &s1,int &s2){
	int i =1 ,j;
	for(i;i < n && HT[i].parent != 0;++i);  		//找到第一個雙親為0的點
	j = i;
	for(i = i+1;i < n && HT[i].parent != 0;++i);	//找到第二個雙親為0的點
	if(HT[i].weight < HT[j].weight){			  	//使s1<s2
		s1 = i;s2 = j;
	}
	else{
		s1 = j;s2 = i;
	}
	while(++i <= n){								//找權值最小的兩個點
		if(HT[i].parent == 0){
			if(HT[i].weight < HT[s1].weight){
				s2 = s1;
				s1 = i;
			}else if(HT[i].weight < HT[s2].weight)
				s2 = i;
		}
	}
}

//建立哈夫曼樹
void CreatTree(HuffmanTree &HT,int n) {
	int m;
	if(n <= 1){
		printf("字元個數不足!\n");
		return;
	}
	m = 2*n-1;
	HT = new HTNode[m+1];		//從1開始,0未用,需要動態分配 m+1 個單元,HT[m]表示根節點
	for(int i = 1;i <= m;i++){  //將雙親、左孩子、右孩子的下標初始化為0
		HT[i].parent = 0;
		HT[i].lChild = 0;
		HT[i].rChild = 0;
	}

	/*------------初始化n個字元及其權值----------- */
	for(int i = 1;i <= n;i++) {
		cout << "請輸入第" << i << "個字元及權值:";
		cin >> HT[i].letter;
		cin >> HT[i].weight;
		while(!cin){
			cin.clear();		//更改cin的狀態標示符
			cin.sync();			//清除快取區的資料流
			cout << "格式錯誤, 重新輸入\n";
			cin >> HT[i].letter;
			cin >> HT[i].weight;
		}
	}
    ofstream fout("hfmTree.txt");
	for(int i = 1;i <= n;i++){
		fout << HT[i].letter << HT[i].weight;
		fout << endl;
	}

	/*------------建立哈夫曼樹----------- */
	int s1,s2;
	for(int i = n+1;i <= m;i++){	//通過n-1次的選擇、刪除、合併來建立哈夫曼樹
		Select(HT,i-1,s1,s2);		//找出權值最小的兩個
		HT[s1].parent = i;
		HT[s2].parent = i;			//將雙親設為i

        HT[i].lChild = s1;
		HT[i].rChild = s2;		  //將其作為左右孩子
        HT[i].weight = HT[s1].weight + HT[s2].weight;	//雙親的權值為左右孩子權值之和
	}
}

//初始字元編碼
void Code(HuffmanTree HT,HuffmanCode &HC,int n) {
    /*-------------------字初始符編碼-----------------------------------*/
    int start,child,parent;
	HC = new char*[n+1];	 			//分配n個字元編碼的編碼表空間
	char* cd = new char[n];  			//分配臨時存放每個字元編碼的動態陣列空間
	cd[n-1] = '\0';         			//編碼結束符
	for(int i = 1;i <= n;i++){
		start = n-1;					//start開始指向最後,即編碼結束符的位置
		child = i;
		parent = HT[i].parent;			//parent指向節點child的雙親節點
		while(parent != 0){
			--start;					//回溯一次start向前指一個位置
			if(HT[parent].lChild == child)
				cd[start] = '0';		//child為parent的左孩子,生成0
			else
				cd[start] = '1';		//child為parent的右孩子,生成1
			child = parent;
			parent = HT[parent].parent;	//繼續向上回溯
		}
		HC[i] = new char[n-start];		//為第i個字元編碼分配空間
		strcpy(HC[i],&cd[start]); 		//將求得的編碼從臨時空間cd複製到HC的行列中
	}
	delete cd;							//釋放臨時空間
	cout << "對應字元編碼為:\n";
	for(int i = 1;i <= n;i++){
		cout << HT[i].letter << ":" << HC[i] << endl;
	}
	/*-------------------------將字元編碼寫入檔案---------------------------------*/
	ofstream fout("CodeFile.txt");
	for(int i = 1;i <= n;i++){
		fout <<  HC[i];
	}
}

//對正文編碼
void TextCode(HuffmanTree HT,HuffmanCode &HC,int n){
	ifstream file;
	file.open("code_text.txt");
	if(!file){					//判斷檔案是否開啟
		cout << "開啟code_text.txt失敗!\n";
		exit(0);
	}

	ofstream fout("code_result.txt");
	int flag;
	char ch;
	while(file.get(ch)){
		for(int i = 1;i <= n;i++){
			if(ch == HT[i].letter){
				fout << HC[i];
				flag = 1;		//標誌flag為1,匹配到初始化的字元
				break;
			}
			else
				flag = 0;
		}
		if(!flag)
			fout << ch;
	}
	cout << "編碼完成\n"
		 << "已初始化的字元由編碼代替\n"
		 << "未初始化的字元不進行替換\n";
}

//譯碼
void Decode(HuffmanTree HT,int n) {
	ifstream file;
	file.open("CodeFile.txt");
	if(!file){					//判斷檔案是否開啟
		cout << "開啟CodeFile1.txt失敗!\n";
		exit(0);
	}

	ofstream fout("TextFile.txt");
	int m = 2*n-1;; //根節點
	char ch;
	while(file.get(ch)){
		if(ch == '1')
			m = HT[m].rChild;
		else if(ch == '0')
			m = HT[m].lChild;
		if(HT[m].lChild == 0 && HT[m].rChild == 0){  //當前欄位解碼完畢
			fout << HT[m].letter;
			m = 2*n-1;
		}
	}
	cout << "譯碼完成\n";
}

//顯示哈夫曼樹
void DisplayTree(HuffmanTree HT,int n) {
	SqStack S;
	InitStack(S);
	int temp,k;
	int m = 2*n-1;					//根節點
    ofstream fout("TreePrint.txt");
	while(m != 0 || StackEmpty(S) == 0){
		while(m != 0){
			Push(S,m); 				//入棧
			m = HT[m].rChild; 		//遍歷右子樹
		}
		if(!StackEmpty(S)){
			Pop(S,m);				//出棧

			k = 0;temp = m;			//計算離根節點距離,美化輸出
			while(temp){
				temp = HT[temp].parent;
				k++;
			}
			for(int i = 1;i < k;i++){
				printf("    ");
			}
            fout<<HT[m].weight<<" ";
			cout << HT[m].weight << endl;
			m = HT[m].lChild; //遍歷左子樹
		}
	}
}

//顯示正文的編碼及譯碼結果
void Result(HuffmanTree HT){
	char ch;
	ifstream fileCode,fileDecode;

	/*-----------正文編碼結果-------------------*/
	/*fileCode.open("code_result.txt");
	if(!fileCode){					//判斷檔案是否開啟
		cout << "開啟code_result.txt失敗!\n";
		exit(0);
	}
	cout << "正文編碼結果:\n";
	while(fileCode.get(ch)){
		cout << ch;
	}
	cout << endl;*/

	/*-----------正文譯碼結果-------------------*/
	fileDecode.open("CodeFile.txt");
	if(!fileDecode){
		cout << "開啟CodeFile.txt失敗!\n";
		exit(0);
	}
	cout << "\n譯碼結果:\n";
    ofstream fout("CodePrin.txt");
	while(fileDecode.get(ch)){
	    fout<<ch;
		cout << ch;
	}
	cout << endl << endl;
}

//選單
void menu() {
	cout << "			        哈夫曼編/譯碼器\n";
	cout << "-------------------------------------------------------------------------------\n";
	cout << "				1 建立哈夫曼樹\n";
	cout << "				2 字元編碼\n";
	cout << "				3 譯碼\n";
	cout << "				4 顯示哈夫曼樹\n";
	cout << "				5 印程式碼檔案\n";
	cout << "				0 退出程式\n";
	cout << "-------------------------------------------------------------------------------\n";
}

int main(int argc, char** argv) {
	int choice,n;
	HuffmanTree Tree;
	HuffmanCode HC;
	while(1){
		menu();
		cout << "\n請選擇對應的功能選項進行相應操作:\n";
		cin >> choice;
		switch(choice){
			//建立哈夫曼樹
			case 1:
				cout << "輸入字元的個數:";
				cin >> n;
				while(!cin){
					cin.clear();
					cin.sync();			//清空流
					cout << "格式錯誤, 重新輸入\n";
					cin >> n;
				}
				CreatTree(Tree,n);

				break;

			//編碼
			case 2:
				if(Tree != NULL)
					Code(Tree,HC,n);
				else
					cout << "還未建立哈夫曼樹\n";
				break;

			//譯碼
			case 3:
				if(Tree != NULL)
					Decode(Tree,n);
				else
					cout << "還未建立哈夫曼樹\n";
				break;

			//顯示哈夫曼樹
			case 4:
				if(Tree != NULL)
					DisplayTree(Tree,n);
				else
					cout << "還未建立哈夫曼樹\n";
				break;
			case 5:
				if(Tree != NULL)
					Result(Tree);
				else
					cout << "還未建立哈夫曼樹\n";
				break;

			case 0:
				exit(0);

			default:
				cout << "請輸入正確的操作序號!\n";
				break;
		}
		system("pause");
		system("cls");
	}
	return 0;
}

四、拓撲排序

1、問題描述

任務

編寫函式實現圖的拓撲排序。

2、程式碼部分

#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
#define MVNum 100
typedef int Status;
typedef char VerTexType;
typedef char OtherInfo;
int indegree[MVNum] = {0};
//建立棧
typedef struct StackNode{
	int data;
	StackNode *next;
}StackNode,*StackList;
//出棧函式
StackList Pop(StackList S, int *e)
{
	StackList p;
	p = S;
	if (!p)
		return ERROR;
	*e = p->data;
	S = S->next;
	free(p);
	return S;
}
//入棧函式:
StackList Push(StackList S,int e)
{
	StackList p;
	p = (StackNode *)malloc(sizeof(StackNode));
	p->data = e;
	p->next = S;
	S = p;
	return S;
}
//鄰接表建立有向圖的實現
//邊結點
typedef struct ArcNode{    //連結串列結點
	int adjvex;           //鄰接表建立無向網的實現
	ArcNode *nextarc;    //指向下一條邊的指標
	OtherInfo info;       //和邊相關的資訊
}ArcNode;
//頂點資訊
typedef struct VNode{   //頭結點
	VerTexType data;   //頂點資訊
	ArcNode *firstarc;//指向第一條依附該頂點的邊的指標
}VNode,AdjList[MVNum];//AdjList 表示鄰接表型別

typedef struct{
	AdjList vertices;     //鄰接表頭結點陣列
	int vexnum, arcnum;   //圖的頂點數和弧數
}ALGraph;
//建立有向圖:
//G帶操作的圖;v要在圖中定位的頂點
int LocateVex(ALGraph *G, VerTexType v)
{
	int i;
	for (i = 0; i < (G->vexnum); i++)
	{
		if (v == G->vertices[i].data)
			return i;               //頂點存在則返回在頭結點陣列中的下標;否則返回
	}
}

void CreateUDG(ALGraph *G)
{
	int i, j, k;
	VerTexType v1, v2;
	ArcNode *p1;
	printf("Enter the total number of nodes and arcs:"); //G帶操作的圖;v要在圖中定位的頂點
	scanf("%d %d", &G->vexnum, &G->arcnum);
	fflush(stdin);    //是清空輸入緩衝區的
	printf("Enter values for each node:");
    for(i=0; i<G->vexnum;i++)   //鄰接表初始化
    {
            scanf("%c", &G->vertices[i].data);
            G->vertices[i].firstarc = NULL;
    }
    for (k = 0; k < G->arcnum; k++)
	{
		fflush(stdin);   //是清空輸入緩衝區的
		printf("Two points and two nodes of radian:");
		scanf("%c %c", &v1, &v2);
		i = LocateVex(G, v1);   //返回這兩個頂點在頂點陣列中的位置
		j = LocateVex(G, v2);
		p1 = (ArcNode *)malloc(sizeof(ArcNode));   //給鄰接表指標分配空間
		p1->adjvex = j;                          //賦值給p->adjvex指向的頂點域
		p1->nextarc = G->vertices[i].firstarc; //nextarc指標域指向i結點的firstarc指標域
		G->vertices[i].firstarc = p1;    //將點i的第一條指標指向
		indegree[j]++;       //vi->vj, vj入度加1
    }
}
//拓撲排序演算法
Status TopologicalSort(ALGraph G, int *topo)
{ //先宣告棧指標S,並讓其指向NULL。檢查所有節點中是否有入度為0的節點,如果有,則進棧。
	int i, m, k;
	StackList S;  //先宣告棧指標S,並讓其指向NULL。
	ArcNode *p;
	S = NULL;
	for (i = 0; i < G.vexnum; i++) //檢查所有節點中是否有入度為0的節點,如果有則進棧。
	{
		if (!indegree[i])  //當陣列不為零時
			S=Push(S, i);
	}    //入度為零去完後
	m = 0; //記錄topu陣列的數
	while (S)//棧不為空的時候,先出棧,取出棧頂元素,並將其記錄在topo[]陣列中
	{
		S=Pop(S, &i);
		topo[m] = i;
		++m;
		p = G.vertices[i].firstarc;   //指標p 指向第一條邊的節點
		while (p != NULL)
		{
			k = p->adjvex;
			--indegree[k];
			if (indegree[k] == 0)
				S=Push(S, k);
			p = p->nextarc;
		}
	}
	topo[m] = -1;  // 為-1時結束
	if (m < G.vexnum)  // topo[]陣列中元素是否已經達到節點數,
		return ERROR;
	else
		return OK;
}

int main(void)
{
	ALGraph G;
	int i;
	int topo[99] = {0};
	CreateUDG(&G);
	if (TopologicalSort(G, topo))
	{  printf("您輸入的為有向圖,無環\n拓撲排序序列為:");
		for (i = 0; topo[i] != -1; i++)
		{
			printf("%c ", G.vertices[topo[i]].data);
		}
	}
	else
		printf("您輸入的為有環,請重新輸入");
	printf("\n");
	return 0;
}
/*
    6 8
    123456
    1 2
    1 3
    1 4
    4 5
    6 5
    6 4
    3 2
    3 5
    shuchu:
        有向圖,無環:
         序列為6 1 3 2 4 5
*/


五、雜湊檔案的插入、刪除和查詢

1、問題描述

功能要求:
  1. 初始化三列檔案;
  2. 向雜湊檔案中插入一個元素;
  3. 從雜湊檔案中刪除一個元素;
  4. 從雜湊檔案中查詢一個元素。

雜湊檔案通常採用連結法處理衝突。

雜湊檔案中每個節點的型別定義為:

Struct  FLNode 
{ //雜湊主檔案中的節點型別
    ElemType  data ; //值域
    Int  next; //指向下一個節點的指標域
};

2、程式碼描述

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <windows.h>
using namespace std;
/*定義散列表長度,其值取決於待三列的元素數量
和選取的裝填因α的大小,在此假定為10*/
#define N 13
/*定義關鍵字型別,這裡假定為整型*/
typedef int KeyType;
struct ElemType     //元素型別
{
    KeyType key;    //關鍵字域
    char rest[10];  //其他域,假定用字元陣列表示
};
struct FLNode   //索引檔案中的結點型別
{
    struct ElemType data;   //值域
    int next;       //指向下一個結點的指標域
};
const int b1 = sizeof(KeyType); //用全域性常量b1儲存索引表文件中的記錄(元素)長度
const int b2 = sizeof(struct FLNode);   //用全域性常量b2儲存索引主檔案中的記錄(結點)長度

//定義雜湊主檔案和索引檔案的名字
char filename[]="Hash"; // 雜湊主檔名
char Iname[]="HashIndex"; // 索引檔名

//建立並初始化散列表檔案
void InitHashFile(char *fname)
{
    // 確認
    char ch;
    printf("確實要重新初始化雜湊檔案(y/n)?");
    getchar();
    scanf("%c", &ch);
    if('y' == ch || 'Y' == ch)
    {
        printf("--- 重新初始化雜湊檔案完畢! ---\n");
    }
    else
    {
        printf("--- 未執行重建雜湊檔案操作! ---\n");
        return;
    }

    /*------確認完成,開始初始化------*/
    int i;
    int *A;
    //以讀寫方式新建二進位制雜湊檔案
    FILE *fp, *fp2;
    fp = fopen(fname, "wb+");
    fp2 = fopen(Iname, "wb+");
    if(NULL == fp|| NULL == fp2)
    {
        printf("開啟檔案失敗!/n");
        exit(1);
    }
    //動態分配具有N+1個整型儲存空間的陣列A
    A = (int *)calloc(N + 1, b1);
    if(NULL == A)
    {
        printf("記憶體申請失敗!/n");
        exit(1);
    }

    //給陣列A中的每個元素賦初值-1,表示空指標
    for(i = 0; i < N + 1; i++)
    {
        A[i] = -1;
    }
    //初始化雜湊檔案
    fwrite((char *)A, (N + 1)*b1, 1, fp);
    fwrite((char *)A, (N + 1)*b1, 1, fp2);
    //刪除陣列A
    free(A);
    //關閉fp對應的檔案
    fclose(fp);
    fclose(fp2);
}

//檢測是否存在雜湊檔案
void existHash(char *fname)
{
    char bcreate;
    char filename[128];
    FILE *fp, *fp2;
    fp = fopen(fname, "rb");
    fp2 = fopen(Iname, "rb");
    if(NULL == fp|| NULL == fp2)
    {
        printf("--- 雜湊檔案不存在, 是否新建雜湊檔案(y:重新建立;/n:開啟其他雜湊檔案)? ---\n");
        scanf("%c", &bcreate);
        if('y' == bcreate || 'Y' == bcreate)
        {
            InitHashFile(fname);
            printf("--- 新建雜湊檔案完畢! ---\n");
        }
        else
        {
            printf("請輸入雜湊檔案路徑:\n");
            scanf("%s", filename);
            strcpy(fname, filename);
            existHash(fname);
        }
    }
    else
    {
        fclose(fp);
        fclose(fp2);
    }
}

//把元素x插入到雜湊檔案中
void HFInsertOne(char *fname, struct ElemType x)
{
    int p;
    int len;    //檔案尾結點位置序號
    int *A;
    int d;
    struct FLNode temp;
    struct FLNode pn;
    //以讀寫和不新建方式開啟雜湊檔案
    FILE *fp, *fp2;
    fp = fopen(fname, "rb+");
    fp2 = fopen(Iname, "rb+");
    if(NULL == fp)
    {
        printf("開啟檔案失敗!\n");
        exit(1);
    }
    //動態分配具有N + 1個整型儲存空間的陣列
    A = (int *)calloc(N + 1, b1);
    if(!A)
    {
        printf("記憶體申請失敗!\n");
        exit(1);
    }
    //將索引檔案的表頭讀入到陣列A中
    fread((char *)A, (N + 1) * b1, 1, fp2);
    //以關鍵字x.key計算x的雜湊地址,採用除留餘數法
    d = x.key % N;
    //以x和A[d]的值構成待插入雜湊檔案的記憶體節點temp
    temp.data = x;
    temp.next = A[d];
    //將temp結點的值寫入到雜湊檔案中,並連結到雜湊檔案表頭
    //下表d單鏈表的表頭
    if(-1 == A[N])
    {
        //將檔案指標移至檔案尾
        fseek(fp, 0L, 2);
        //計算出檔案尾的結點位置序號
        len = (ftell(fp) - (N+1)*b1)/b2;
        //將temp結點的值寫入檔案尾
        fwrite((char *)&temp, b2, 1, fp);
        //使A[d]指向新插入的結點
        A[d] = len;
    }
    else
    {
        //p指向空閒單鏈表的表頭結點
        p = A[N];
        //使空閒單鏈表的表頭指標指向其下一個結點
        fseek(fp, b1 * (N+1) + p*b2, 0);
        fread((char *)&pn, b2, 1, fp);
        A[N] = pn.next;
        //使temp的值寫入到p位置的結點上
        fseek(fp, -b2, 1);
        fwrite((char *)&temp, b2, 1, fp);
        //使A[p]指向新插入的p結點
        A[d] = p;
    }
    //將陣列A中的全部內容寫回到索引檔案的表頭中
    fseek(fp,0L,0);
    fseek(fp2,0L,0);
    fwrite((char *)A, b1 * (N+1), 1, fp2);
    //刪除動態陣列A和關閉雜湊檔案
    free(A);
    fclose(fp);
    fclose(fp2);
}

//從雜湊檔案中刪除關鍵字尾x.key的元素,並由x帶回該
//元素,若刪除成功則返回1,否則返回0
int HFDelete(char *fname, struct ElemType *x)
{
    struct FLNode tp,tq;
    int p, q;
    int *A;
    int d;
    FILE *fp, *fp2;
    //開啟雜湊檔案
    fp = fopen(fname, "rb+");
    fp2 = fopen(Iname, "rb+");
    if(NULL == fp|| NULL == fp2)
    {
        printf("開啟檔案失敗!\n");
        exit(1);
    }
    //申請動態陣列A
    A = (int *)calloc(N+1, b1);
    if(NULL == A)
    {
        printf("記憶體申請失敗!\n");
        exit(1);
    }
    //將索引檔案表頭讀入陣列A中
    fread((char *)A, (N+1)*b1, 1, fp2);

    //計算雜湊地址d
    d = x->key % N;
    p = A[d];
    while(-1 != p)
    {
        fseek(fp, (N+1)*b1+p*b2, 0);
        fread((char*)&tp, b2, 1, fp);
        if(tp.data.key  == x->key)
        {
            //被刪除結點的元素值賦給x帶回
            *x = tp.data;
            //從單鏈表中刪除p結點
            if(p == A[d])
            {
                A[d] = tp.next;
            }
            else
            {
                tq.next = tp.next;
                fseek(fp, (N+1)*b1+q*b2, 0);
                fwrite((char *)&tq, b2, 1, fp);
            }
            //將p結點連線到空閒單鏈表的表頭
            tp.next = A[N];
            fseek(fp, (N+1)*b1+p*b2, 0);
            fwrite((char *)&tp, b2, 1,fp);
            A[N] = p;
            //結束while迴圈
            break;
        }
        else
        {
            //使p指標的值賦給q,tp結點的值賦值給tq結點
            q = p;
            tq = tp;
            //p指向單鏈表中的下一個結點
            p = tp.next;
        }//if分支結束
    }//while迴圈結束
    //將索引檔案的表頭重新寫入索引檔案
    fseek(fp, 0L, 0);
    fseek(fp2, 0L, 0);
    fwrite((char *)A, (N + 1) * b1, 1, fp2);
    //釋放A陣列申請的記憶體空間
    free(A);
    //關閉雜湊檔案
    fclose(fp);
    fclose(fp2);
    if(-1 == p)
    {
        return 0;   //沒有找到要刪除的結點
    }
    else
    {
        return 1;   //成功刪除要刪除的結點
    }//<end if>
}

//從雜湊檔案中查詢關鍵字為x.key的元素,並由x帶回
//元素,若查詢成功則返回1,否則返回0
int HFSearch(char *fname, struct ElemType *x)
{
    int d;
    int p;
    int *A;
    struct FLNode temp;
    //以讀寫方式開啟雜湊檔案
    FILE *fp, *fp2;
    fp = fopen(fname, "rb+");
    fp2 = fopen(Iname, "rb+");
    if(NULL == fp|| NULL == fp2)
    {
        printf("開啟檔案失敗!\n");
        exit(1);
    }
    //申請動態陣列A
    A = (int *)calloc(N+1, b1);
    if(NULL == A)
    {
        printf("記憶體申請失敗!\n");
        exit(1);
    }
    fread((char *)A, (N+1)*b1, 1, fp2);
    d = x->key % N;
    //取出地址為d的單鏈表的表頭指標(指向該地址的第一個儲存元素)
    p = A[d];
    //從d點連結串列中查詢關鍵字為x.key的元素
    while(p != -1)
    {
        fseek(fp, (N+1)*b1 + p*b2, 0);//在檔案中定位
        fread((char *)&temp, b2, 1, fp);
        if(temp.data.key == x->key)
        {
            *x = temp.data; //被查詢到的元素由x帶回
            break;
        }
        else
        {
            p = temp.next;  //把結點指標移到下一個結點
        }
    }
    //釋放A陣列申請的記憶體空間
    free(A);
    //關閉檔案
    fclose(fp);
    fclose(fp2);
    if(-1 == p)
    {
        return 0;
    }
    else
    {
        return 1;
    }//if分支結構結束
}
//順序打印出雜湊檔案中的每個單鏈表中的每個結點位置序號及元素值
void HFPrint(char *fname)
{
    int i;
    int p;
    int *A;
    struct FLNode pn;
    //以讀寫方式開啟雜湊檔案
    FILE *fp, *fp2;
    fp = fopen(fname, "rb+");
    fp2 = fopen(Iname, "rb+");
    if(NULL == fp|| NULL == fp2)
    {
        printf("開啟檔案失敗!\n");
        exit(1);
    }

    //申請動態陣列A
    A = (int *)calloc(N+1, b1);
    if(NULL == A)
    {
        printf("記憶體申請失敗!\n");
        exit(1);
    }
    fread((char *)A, b1, N+1, fp2);
    for(i = 0; i < N+1; i++)
    {
        printf("%d:", i);
        p = A[i];
        while(-1 != p)
        {
            fseek(fp, (N+1)*b1 + p*b2, 0);  // 修改檔案指標位置
            fread((char *)&pn, b2, 1, fp);  // 從檔案中中讀取節點
            printf("%d->%d(%s)  ", p, pn.data.key, pn.data.rest);
            p = pn.next;
        }
        printf("\n");
    }

    //刪除動態陣列A申請的記憶體
    free(A);
    //關閉檔案
    fclose(fp);
    fclose(fp2);
}

void Insert(char filename[])
{
    struct ElemType x;
    printf("輸入待插入元素x的值(作為關鍵字):\n");
    scanf("%d", &x.key);
    printf("輸入數值:\n");
    scanf("%s", x.rest);
    if(!HFSearch(filename,&x))
        HFInsertOne(filename, x);
    else
    {
        printf("該值在雜湊表已存在,您將更新這個數值");
        printf("輸入待插入元素x的值(作為關鍵字):\n");
        scanf("%d", &x.key);
        printf("輸入數值:\n");
        scanf("%s", x.rest);
    }
}
void Delete(char filename[])
{
    struct ElemType x;
    //定義tag用於儲存或查詢函式的返回值
    int tag;

    printf("輸入待刪除元素x的關鍵字:");
    scanf("%d", &x.key);
    tag = HFDelete(filename, &x);
    if(1 == tag)
    {
        printf("--- 刪除成功! %d %s ---\n", x.key, x.rest);
    }
    else
    {
        printf("--- 刪除失敗 ---\n");
    }//<end if>
}
void Search(char filename[])
{// 待完善,rest(其餘資訊)查詢
    struct ElemType x;
    //定義tag用於儲存或查詢函式的返回值
    int tag;

    printf("輸入待查詢元素x的關鍵字:\n");
    scanf("%d", &x.key);
    tag = HFSearch(filename, &x);
    if(1 == tag)
    {
        printf("---it's sucessful!%d %s ---\n", x.key, x.rest);
    }
    else
    {
        printf("error!\n");
    }
}

void start()
{// 開始
    int number; //選擇的功能號表

    //檢測雜湊檔案是否存在
    existHash(filename);

    while(1)
    {
        // print meum
        printf("\n");
        printf("\t************************************\n");
        printf("\t*************雜湊檔案 **************\n");
        printf("\t|**1** 初始化雜湊檔案 **************\n");
        printf("\t|**2** 向雜湊檔案中插入一個元素*****\n");
        printf("\t|**3** 從雜湊檔案中刪除一個元素 ---|\n");
        printf("\t|**4** 從雜湊檔案中查詢一個元素 ---|\n");
        printf("\t|**5** 列印雜湊檔案 ---------------|\n");
        printf("\t|**0** 結束執行 -------------------|\n");
        printf("\t+----------------------------------+\n");
        printf("*--請輸入你的選擇(0-5):");

        scanf("%d", &number);
        //fflush(stdin);// 防止錯誤輸入
        switch(number)
        {
        case 0:
            return ;
        case 1:
            //初始化雜湊檔案
            InitHashFile(filename);
            break;
        case 2:
            //向雜湊檔案中插入一個元素
            Insert(filename);
            break;
        case 3:
            //從雜湊檔案中刪除一個元素
            Delete(filename);
            break;
        case 4:
            //從雜湊檔案中查詢一個元素
            Search(filename);
            break;
        case 5:
            //列印雜湊檔案
            HFPrint(filename);

            break;
        default:
            printf("\n--- 輸入功能號表錯誤 ---\n");
            break;
        }   //switch結束o
        //system("pause"); 不能使用 test data 連續輸入
        printf("contiue");
        getch();
        system("cls");
    }
}

int main()
{

    start();

    return 0;
}

/*

2
10 a
2
11 b
2
12 c
2
13 d
2
14 e
2
15 f
2
16 g
2
17 h
2
18 i
2
19 p
2
20 k
2
21 l
2
22 m
2
23 n
*/