Как стать автором
Обновить

Алгоритм. Очередь с приоритетом

Время на прочтение5 мин
Количество просмотров19K

В этой статье я покажу очередь с приоритетом с использованием linked list. Алгоритм простой и позволяет получить приоритетные сообщения раньше, чем остальные сообщения.

Я сначала поискал в интернете реализацию такого алгоритма. Первое, что я нашел, это решение на c++ с обычными массивами. То есть создается шаблонный класс и в нем создается два массива, один для чисел, другой для приоритетности. Мне показалось, что решение не очень хорошее, потому что при добавлении нового числа, создается новый массив и из старого копируются данные в новый массив. Чем больше очередь, тем больше копирования каждый раз. Ещё проблема при использовании массива - это что нельзя использовать в другом потоке очередь, пока ты не выполнишь полностью функцию вставки данных в очередь, так как смениться указатель на массив и так далее.

Алгоритм - очередь с приоритетом достаточно простой, мы создаем структуру, я опять буду показывать пример кода на C, и входящие данные будут числа. Итак.

struct queue {
	unsigned long number;
	unsigned long priority;
	struct queue *parent;
	struct queue *child;
};

Также создаем глобальный указатель на начало очереди, прошу заметить, это всего лишь пример, разумеется в C++ можно создать это в классе, но так как в C будет только одна очередь, то она становиться глобальной как само собой разумеющееся. И также создаем pri указатель на указатель. Он будет хранить хвост каждого приоритета, чтобы можно было в linked-list сразу в нужное место всегда добавлять новые данные.

struct queue *root;
struct queue **pri;

теперь создаем функцию и начальные значения. Здесь мы также ищем близжайший приоритет. например если у нас первым был приоритет 20, потом 14, а теперь 10. то мы линейно доходим до 14 и становимся в его хвост и добавляемся уже рядом с ним.

static void add_number (int number, int priority, int max)
{
	register struct queue *n = root;
	register struct queue *prev = NULL;
	register struct queue *temp = NULL;
	register struct queue *temp1 = NULL;
	int prr = priority + 20;
	while ((n = pri[prr]) == NULL) 
	{ 
		prr++; 
		if (prr >= max) break;
	}
  if (n == NULL) n = root;

Теперь можно в бесконечном цикле идти по списку и добавлять в нужную позицию наше число. Начнем с того, если указатель n равен нулю.

		if (n == NULL)
		{
			n = calloc (1, sizeof (struct queue));
			n->number = number;
			n->priority = priority;
			if (prev)
			{
				prev->child = n;
				n->parent = prev;
			}
			pri[priority + 20] = n;
			if (root == NULL) root = n;
			return;
		}

Здесь в указываем чтобы root указывал на него как на начало. Отсюда начнется следующий отсчет.

Далее мы смотрим, если добавляемый приоритет выше, чем наше сообщение в очереди, то перед ним установим новый приоритет.

		while (n->priority < priority)
		{
			temp = n;
			prev = n;
			n = n->parent;
			if (n == NULL)
			{
				n = calloc (1, sizeof (struct queue));
				n->number = number;
				n->priority = priority;
				n->child = temp;
				temp->parent = n;
				root = n;
				pri[priority + 20] = n;
				return;
			}
		}

Также можно заметить, что в этом случае root опять меняет свою позицию, указав себя как начало в списке.

Далее смотрим если добавляемый приоритет ниже чем текущий приоритет.

		while (n->priority > priority) 
		{
			temp = n;
			prev = n;
			n = n->child;
			if (n == NULL)
			{
				n = calloc (1, sizeof (struct queue));
				n->number = number;
				n->priority = priority;
				n->parent = temp;
				temp->child = n;
				pri[priority + 20] = n;
				return;
			} else if (n->priority <= priority)
			{
				temp = n->parent;
				temp1 = n;
				n = calloc (1, sizeof (struct queue));
				n->number = number;
				n->priority = priority;
				n->parent = temp;
				n->child = temp1;
				temp->child = n;
				temp1->parent = n;
				pri[priority + 20] = n;
				return;
			}
		}

Здесь всё должно быть понятно, что мы добавляем наше число куда нужно.

И осталось последнее.

		while (n->priority == priority)
		{
			temp = n;
			prev = n;
			n = n->child;
			if (n == NULL)
			{
				n = calloc (1, sizeof (struct queue));
				n->number = number;
				n->priority = priority;
				n->child = NULL;
				n->parent = n;
				temp->child = n;
				pri[priority + 20] = n;
				return;
			} else if (n->priority != priority)
			{
				temp = n->parent;
				temp1 = n;
				n = calloc (1, sizeof (struct queue));
				n->number = number;
				n->priority = priority;
				n->parent = temp;
				n->child = temp1;
				pri[priority + 20] = n;
				return;
			}
		}

Здесь мы смотрим, если он равен нашему приоритету, то дадим сначала выйти из очереди первому добавленному с таким приоритетом, а этот добавим за ним.

Теперь создадим функцию по выдачи из очереди нашего числа.

static int get_number_from_queue ()
{
	if (!root) return -1;
	register struct queue *n = root;
	root = root->child;
	register unsigned long number = n->number;
	free (n);
	return number;
}

Всё, здесь мы выдаем номер и смещаем наш root. Так как это без много поточности, то здесь я не использовал мьютексы, но по хорошему, нужно поставить мьютекс на смену root и всё.

Теперь создадим остальное.

struct arr {
	int num;
	int priority;
} arr[] = {
	{ -14, -14},
	{ -4, -4},
	{ 0, 0 },
	{ 1, 1 },
	{ 2, 2 },
	{ 3, 3 },
	{ 4, 4 },
	{ 14, 14 },
	{ 5, 5 },
	{ 5, 5 },
	{ 6, 6 },
	{ 7, 7 },
	{ 8, 8 },
	{ 9, 9 },
	{ 10, 10 }
};

int main (int argc, char **argv)
{
	pri = calloc (41, sizeof (struct queue *));
	for (int i = 0; i < 41; i++)
	{
		pri[i] = NULL;
	}

	int size = sizeof (arr) / sizeof (struct arr);
	for (int i = 0; i < size; i++)
	{
		add_number (arr[i].num, arr[i].priority);
	}

	for (int i = 0; i < size; i++)
	{
		printf ("%d: %d\n", i, get_number_from_queue());
	}
}

Чтобы было удобней проверить код, то выкладываю полный код.

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

struct queue {
	unsigned long number;
	long priority;
	struct queue *parent;
	struct queue *child;
};

struct queue *root;
struct queue **pri;

static void add_number (int number, int priority)
{
	register struct queue *n = root;
	register struct queue *prev = NULL;
	register struct queue *temp = NULL;
	register struct queue *temp1 = NULL;
	int prr = priority + 20;
	while ((n = pri[prr]) == NULL) 
	{ 
		prr++; 
		if (prr >= max) break;
	}
  if (n == NULL) n = root;
	while (1)
	{
		if (n == NULL)
		{
			n = calloc (1, sizeof (struct queue));
			n->number = number;
			n->priority = priority;
			if (prev)
			{
				prev->child = n;
				n->parent = prev;
			}
			pri[priority + 20] = n;
			if (root == NULL) root = n;
			return;
		}
		while (n->priority < priority)
		{
			temp = n;
			prev = n;
			n = n->parent;
			if (n == NULL)
			{
				n = calloc (1, sizeof (struct queue));
				n->number = number;
				n->priority = priority;
				n->child = temp;
				temp->parent = n;
				root = n;
				pri[priority + 20] = n;
				return;
			}
		}
		while (n->priority > priority) 
		{
			temp = n;
			prev = n;
			n = n->child;
			if (n == NULL)
			{
				n = calloc (1, sizeof (struct queue));
				n->number = number;
				n->priority = priority;
				n->parent = temp;
				temp->child = n;
				pri[priority + 20] = n;
				return;
			} else if (n->priority <= priority)
			{
				temp = n->parent;
				temp1 = n;
				n = calloc (1, sizeof (struct queue));
				n->number = number;
				n->priority = priority;
				n->parent = temp;
				n->child = temp1;
				temp->child = n;
				temp1->parent = n;
				pri[priority + 20] = n;
				return;
			}
		}
		while (n->priority == priority)
		{
			temp = n;
			prev = n;
			n = n->child;
			if (n == NULL)
			{
				n = calloc (1, sizeof (struct queue));
				n->number = number;
				n->priority = priority;
				n->child = NULL;
				n->parent = n;
				temp->child = n;
				pri[priority + 20] = n;
				return;
			} else if (n->priority != priority)
			{
				temp = n->parent;
				temp1 = n;
				n = calloc (1, sizeof (struct queue));
				n->number = number;
				n->priority = priority;
				n->parent = temp;
				n->child = temp1;
				pri[priority + 20] = n;
				return;
			}
		}
	}
}

static int get_number_from_queue ()
{
	if (!root) return -1;
	register struct queue *n = root;
	root = root->child;
	register unsigned long number = n->number;
	free (n);
	return number;
}

struct arr {
	int num;
	int priority;
} arr[] = {
	{ -14, -14},
	{ -4, -4},
	{ 0, 0 },
	{ 1, 1 },
	{ 2, 2 },
	{ 3, 3 },
	{ 4, 4 },
	{ 14, 14 },
	{ 5, 5 },
	{ 5, 5 },
	{ 6, 6 },
	{ 7, 7 },
	{ 8, 8 },
	{ 9, 9 },
	{ 10, 10 }
};

int main (int argc, char **argv)
{
	pri = calloc (41, sizeof (struct queue *));
	for (int i = 0; i < 41; i++)
	{
		pri[i] = NULL;
	}

	int size = sizeof (arr) / sizeof (struct arr);
	for (int i = 0; i < size; i++)
	{
		add_number (arr[i].num, arr[i].priority);
	}

	for (int i = 0; i < size; i++)
	{
		printf ("%d: %d\n", i, get_number_from_queue());
	}
}

разумеется надо знать какие приоритеты доступны, то есть например я выбираю, что доступны приоритеты от -20 до 20. создаю массив pri и заполняю нулями указатели. Когда мы вставляем новое число с приоритетом, то оно сравнивается, есть ли такой приоритет, если есть, то мы попадаем сразу в конец этого приоритета и добавляем свой.

Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 14: ↑3 и ↓11-5
Комментарии14

Публикации

Истории

Работа

Программист С
29 вакансий
QT разработчик
9 вакансий
Программист C++
110 вакансий

Ближайшие события

12 – 13 июля
Геймтон DatsDefense
Онлайн
19 сентября
CDI Conf 2024
Москва