1. 程式人生 > 其它 >資料結構之優先佇列(線性結構的優先佇列,陣列實現和連結串列實現)

資料結構之優先佇列(線性結構的優先佇列,陣列實現和連結串列實現)

技術標籤:資料結構佇列資料結構連結串列

優先佇列(priority queue)

普通的佇列是一種先進先出的資料結構,元素在佇列尾追加,而從佇列頭刪除。在優先佇列中,元素被賦予優先順序。當訪問元素時,具有最高優先順序的元素最先刪除。優先佇列具有最高階先出 (first in, largest out)的行為特徵

優先佇列是0個或多個元素的集合,每個元素都有一個優先權或值,對優先佇列執行的操作有1) 查詢;2) 插入一個新元素;3) 刪除.在最小優先佇列(min priority queue)中,查詢操作用來搜尋優先權最小的元素,刪除操作用來刪除該元素;對於最大優先佇列(max priority queue),查詢操作用來搜尋優先權最大的元素,刪除操作用來刪除該元素.優先權佇列中的元素可以有相同的優先權,查詢與刪除操作可根據任意優先權進行.

優先佇列經常通過堆的方式來實現,查詢和刪除的時間複雜度都是log2n,所以很快

另一種描述方法是採用有序線性表,當元素按遞增次序排列,使用連結串列時則按遞減次序排列,這兩種描述方法的刪除時間均為( 1 ),插入操作所需時間為(n).

基礎概念就是這麼些了,因為我現在總結的是線性結構,所以我們來用線性的結構實現這個資料結構

我先來用陣列的方式來實現一下
我來引進一個問題,任務排程問題,程序i需要pri的時間,求各任務等待的時間加起來最小的排序

下面是我的測試資料檔案:
在這裡插入圖片描述

基礎概念裡面說到,資料都有優先順序,所以我們就想到了,資料就分為了兩部分:資料本身,優先權。
所以我們將它封裝成一個結構體:

struct data {
	int num;
	int prior;//優先權
};

接下來是優先佇列這個資料結構的結構體封裝,我們實現的是陣列的優先佇列,它和陣列佇列有一樣的性質,那麼就是下面這樣的結構:

struct prqueue {
	struct data datanum[MAX];
	int size;
};

建立優先佇列的函式:其實就是陣列的初始化和大小的初始化

struct prqueue* createqueue() {
	struct prqueue* newqueue = (struct prqueue*)malloc(sizeof(struct prqueue));
newqueue->size = 0; memset(newqueue->datanum, 0, sizeof(data) * MAX); return newqueue; }

接下來是查詢佇列是否為空:

bool isempty(struct prqueue* pqueue) {
	return pqueue->size == 0;
}

接下來是入隊操作,也就是陣列佇列的常規入隊:

void push(struct prqueue* pqueue, struct data pdata) {
	if (pqueue->size + 1 == MAX) {
		printf("隊滿無法入隊");
		return;
	}//陣列是要判斷一下隊滿的情況的
	pqueue->datanum[pqueue->size].num = pdata.num;
	pqueue->datanum[pqueue->size].prior = pdata.prior;
	pqueue->size++;
}

出隊的操作,刪除和寫入變數的函式,我們利用傳入一級指標來讀取資料:
優先隊列出隊出去的是優先順序最高的數,我這裡權值最小的就是我們所需要的優先順序最高的
所以就是找到最小的權值的資料:

void pop(struct prqueue* pqueue, struct data* pdata) {
	if (isempty(pqueue)) {
		printf("隊為空,無法出隊");
		return;
	}
	int minindex = 0;
	struct data mind = pqueue->datanum[0];
	for (int i = 1; i < pqueue->size; i++) {
		if (mind.prior > pqueue->datanum[i].prior) {
			mind = pqueue->datanum[i];
			minindex = i;
		}
	}//選擇排序,找到優先順序最小的
	pdata->num = mind.num;
	pdata->prior = mind.prior;
	for (int i = minindex; i < pqueue->size; i++) {
		pqueue->datanum[i] = pqueue->datanum[i + 1];
	}//偽刪除,所刪除的元素後面的陣列集體往前移動
	pqueue->size--;
}

接下來是主函式來實現上面的問題排序

int main() {
	struct prqueue* pqueue = createqueue();
	struct data readdata;
	FILE* fp = fopen("test.txt", "r");
	if (fp == NULL) {
		printf("無法開啟資料夾");
		return 0;
	}
	while (fscanf(fp, "%d %d\n", &readdata.num, &readdata.prior) != EOF){
		push(pqueue, readdata);
	}
	fclose(fp);
	int workindex = 1;
	while (!isempty(pqueue)) {
		pop(pqueue, &readdata);
		printf("\t%d\t%d\t%d\n", workindex, readdata.num, readdata.prior);
		workindex++;
	}
	return 0;
}

以下是結果:
在這裡插入圖片描述

我上面提到,線性結構可以實現優先佇列,那麼除了陣列,是不是還有一種結構?
對,連結串列,讓我們來看一下,用連結串列來實現這個問題的優先佇列

首先就是節點和優先佇列的連結串列結構體實現和建立:
我用的是雙向連結串列,因為這樣在插入的時候根據優先順序自動選擇插入更加方便(但是隨之而來容易出現異常和bug)

struct node {
	struct node* front;
	struct data ndata;
	struct node* next;
};//雙向連結串列的節點的結構體
struct listpriqueue {
	struct node* headnode;
	struct node* tailnode;
	int size;
};//鏈式優先佇列的結構體
bool isempty(struct listpriqueue* plist) {
	return plist->size == 0;
}//判斷是否為空的萬金油函式
struct node* createnode(struct data pdata) {
	struct node* newnode = (struct node*)malloc(sizeof(struct node));
	newnode->next = newnode->front = NULL;
	newnode->ndata = pdata;
	return newnode;
}//建立新節點,初始化新節點
struct listpriqueue* creatlistpriqueue() {
	struct listpriqueue* newqueue = (struct listpriqueue*)malloc(sizeof(struct listpriqueue));
	newqueue->headnode = newqueue->tailnode=NULL;
	newqueue->size = 0;
	return newqueue;
}//初始化新鏈式優先佇列並建立返回

接下來是重頭戲,就是鏈式優先佇列的插入,依照優先順序插入,也就是雙向連結串列的索引插入法,這裡只不過運用了比較的方式找到合適點插入:

void push(struct listpriqueue* plist, struct data pdata) {
	struct node* newnode = createnode(pdata);
	if (isempty(plist)) {
		plist->headnode = plist->tailnode = newnode;
	}
	//這裡一定要分好情況,要不然各種指標指向NULL,或者是出不來資料,死迴圈問題,小心,一定要理解我下面的東西
	else {
		struct node* pmove = plist->headnode;
		while (pmove != plist->tailnode) {
			if (newnode->ndata.prior < pmove->ndata.prior && pmove == plist->headnode) {//當比頭節點還小,也就是,要插入的資料優先順序最大的時候,就是頭節點插入法
				newnode->next = plist->headnode;
				plist->headnode->front = newnode;
				plist->headnode = newnode;
			}
			if (newnode->ndata.prior < pmove->ndata.prior) {//在中間的的位置找到了,就是雙向連結串列的合適位置插入,可以參照我之前的部落格
				newnode->front = pmove->front;
				pmove->front->next = newnode;
				newnode->next = pmove;
				pmove->front = newnode;
				break;
			}
			pmove = pmove->next;//一定要移動,要不然插入的是個屁,我就這裡沒表示,然後一直給我拋異常,我就好久才發現
		}
		if (pmove == plist->tailnode) {//當沒找到合適位置,意味著,優先順序最小,就是尾結點插入法
			newnode->front = plist->tailnode;
			plist->tailnode->next = newnode;
			newnode->next = NULL;
			plist->tailnode = newnode;
		}
	}
	plist->size++;
}

接下來就是出隊操作,就是平常的鏈式隊列出隊,也就是頭節點刪除法:

void pop(struct listpriqueue* plist, struct data* pdata) {
	if (isempty(plist)) {
		printf("隊為空,無法出棧");
		return;
	}
	*pdata = plist->headnode->ndata;
	struct node* deletenode = plist->headnode;
	if (plist->size == 1) {
		free(deletenode);
		plist->headnode = NULL;
	}
	else {
		plist->headnode = plist->headnode->next;
		free(deletenode);
	}
	plist->size--;
}

啊,這時候,螢幕面前一定有長得非常帥的小夥子問了哈,為什麼你陣列那裡要出隊的時候實現優先順序最大出列,到這就成了入隊的時候優先順序在前了呢。
我來分析一波,插入的時候是不是找到合適位置就可以停下來了,而出隊的時候需要掃描完才能確定,所以,插入實現優先最好情況是O(1)最壞情況是O(n),但是出隊實現優先的是O(n),不管是不是最壞還是最好(排好的情況),所以,某種意義上是插入的時候實現最好。

下面是主函式來實現上面的問題:

int main() {
	struct listpriqueue* pqueue = creatlistpriqueue();
	struct data readdata;
	FILE* fp = fopen("test.txt", "r");
	if (fp == NULL) {
		printf("無法開啟資料夾");
		return 0;
	}
	while (fscanf(fp, "%d %d\n", &readdata.num, &readdata.prior) != EOF){
		push(pqueue, readdata);
	}
	fclose(fp);
	int workindex = 1;
	while (!isempty(pqueue)) {
		pop(pqueue, &readdata);
		printf("\t%d\t%d\t%d\n", workindex, readdata.num, readdata.prior);
		workindex++;
	}
	return 0;
}

好了線性的優先佇列就到這了。