Сортировка двусвязных списков в c - PullRequest
4 голосов
/ 16 февраля 2012

Я хочу сохранить связанный список в отсортированном порядке при вставке элементов (около 200000 элементов в списке), какой алгоритм вы можете порекомендовать?Я сделал простую реализацию, используя сортировку вставками, но ее производительность очень и очень плохая (большая загрузка процессора).

Спасибо за вашу помощь.

Я провел некоторое сравнение между сортировкой слиянием и вставкойсортировка, но кажется, что сортировка вставки имеет лучшую производительность, я немного смущен этим результатом.Можете ли вы сказать мне, что не так, и если есть лучший алгоритм?

Мой код (для простоты я исключил узел prev в структуре узлов):

struct node {
    int number;
    struct node *next;
};

Сортировка вставки:

void insert_node(int value) {
    struct node *new_node = NULL;
    struct node *cur_node = NULL;
    struct node *last_node = NULL;
    int found; /* 1 means found a place to insert the new node in, 0 means not*/


    new_node = (struct node *)malloc(sizeof(struct node *));
    if(new_node == NULL) {
        printf("memory problem\n");
    }
    new_node->number = value;
    /* If the first element */
    if (head == NULL) {
        new_node->next = NULL;
        head = new_node;
    } 

    else if (new_node->number < head->number) {
        new_node->next = head;
        head = new_node;    
    } 

    else {
        cur_node = head;
        found = 0;
        while (( cur_node != NULL ) && ( found == 0 )) {
            if( new_node->number < cur_node->number )
            {
                found = 1;
            }
            else
            {
                last_node = cur_node;
                cur_node = cur_node->next;
            }
        }
    /* We got the right place to insert our node */
    if( found == 1 )
    {
        new_node->next = cur_node; 
    }
    /* Insert at the tail of the list */
    else
    {
        last_node->next = new_node;
        new_node->next = NULL;
    }           
}

Сортировка слиянием:

/* add a node to the linked list */
struct node *addnode(int number, struct node *next) {
    struct node *tnode;

    tnode = (struct node*)malloc(sizeof(*tnode));

    if(tnode != NULL) {
        tnode->number = number;
        tnode->next = next;
    }

    return tnode;
}

/* perform merge sort on the linked list */
struct node *merge_sort(struct node *head) {
    struct node *head_one;
    struct node *head_two;

    if((head == NULL) || (head->next == NULL))
        return head;

    head_one = head;
    head_two = head->next;
    while((head_two != NULL) && (head_two->next != NULL)) {
        head = head->next;
        head_two = head->next->next;
    }
    head_two = head->next;
    head->next = NULL;

    return merge(merge_sort(head_one), merge_sort(head_two));
}

/* merge the lists.. */
struct node *merge(struct node *head_one, struct node *head_two) {
    struct node *head_three;

    if(head_one == NULL)
        return head_two;

    if(head_two == NULL)
        return head_one;

    if(head_one->number < head_two->number) {
        head_three = head_one;
        head_three->next = merge(head_one->next, head_two);
    } else {
        head_three = head_two;
        head_three->next = merge(head_one, head_two->next);
    }

    return head_three;
}

Ответы [ 9 ]

6 голосов
/ 16 февраля 2012

Для вставки элементов в связанный список условие сортировки не помогает!Там нет алгоритма, чтобы помочь.

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

Если вы хотите вставить множество элементов в большой отсортированный связанный список, вы можете отсортировать их, а затем выполнить очень быстрое слияние O (N).

2 голосов
/ 16 февраля 2012

Для онлайн-решения (вставляйте элементы по мере их поступления) сбалансированное двоичное дерево может быть хорошим вариантом.Это позволяет вставлять (а также удалять) за время O (Log (N)).

В противном случае MergeSort может быть применен к полному списку.

В обоих случаях O (N.Log(N)) всего сравнений.

1 голос
/ 19 февраля 2012

Вы неправильно реализуете сортировку слиянием, которая основана на рекурсивном разделении списка на две части, их сортировке и объединении результата.Но в вашем коде вы на самом деле не делите список на две половины.

Обратите внимание, что в строках:

while((head_two != NULL) && (head_two->next != NULL)) {
    head = head->next;
    head_two = head->next->next;
}
head_two = head->next;
head->next = NULL;

вы выходите из цикла while, когда head_two достигает концаlist: Если, например, вы достигаете head_two->next == NULL в цикле, вы выходите из него с помощью head->next->next == NULL.И когда вы запускаете head_two = head->next;, вы получаете head_two такой, что head_two->next == NULL, который является последним элементом в списке.

Это означает, что вы в основном выполняете сортировку вставкой, а не сортировку слиянием.

Поэтому постарайтесь отслеживать длину списка, добавив параметр length к функции merge_sort, чтобы можно было разбить его на 2. Вот хорошее объяснение алгоритма в википедии .

0 голосов
/ 12 июля 2012

Мне нравятся кучи таких вещей. Вставка и поиск - O (log (n)), а обход - O (n). Свойство heapness обеспечивает постоянную сортировку данных. Вы можете перемещаться вперед и назад, чтобы получить преимущества двусвязного списка.

0 голосов
/ 10 июля 2012

Я внес необходимые изменения в ваш код.Я проверил это, и он отлично работает.Вы должны быть в состоянии закрыть запрос сейчас.

struct node *insert_node( struct node *head, int *value )
{
        struct node *new_node = NULL;
        struct node *cur_node = NULL;
        struct node *last_node = NULL;
        int found; /* 1 means found a place to insert the new node in, 0 means not*/
        new_node = (struct node *)malloc(sizeof(struct node *));
        if(new_node == NULL)
        {
                printf("memory problem\n");
        }
        new_node->number = *value;
        /* If the first element */
        if (head == NULL)
        {
                new_node->prev = new_node->next = NULL;
                head = new_node;
        }
        else if (new_node->number < head->number)
        {
                new_node->next = head;
                head = new_node;
        }
        else
        {
                cur_node = head;
                found = 0;
                while (( cur_node != NULL ) && ( found == 0 ))
                {
                        if( new_node->number < cur_node->number )
                                found = 1;
                        else
                        {
                                last_node = cur_node;
                                cur_node = cur_node->next;
                        }
                }
                /* We got the right place to insert our node */
                if( found == 1 )
                {
                        new_node->next = cur_node;
                        new_node->prev = last_node;
                        last_node->next = new_node;
                        cur_node->prev = new_node;
                }
                /* Insert at the tail of the list */
                else
                {
                        last_node->next = new_node;
                        new_node->next = NULL;
                        new_node->prev = last_node;
                }
        }
        return head;
}
0 голосов
/ 21 февраля 2012

на самом деле вы не хотите сортировать список. Merge Sort используется для сортировки несортированного списка. (некоторые здесь уже указали на это).

Чтобы сохранить список отсортированным, вы должны вставить каждый элемент в нужную позицию. так что в основном вы должны:

  1. поиск места для вставки новой записи
  2. вставить

Сложность здесь в алгоритме поиска.

Таким образом, двоичное дерево или даже лучше дерево AVL (http://en.wikipedia.org/wiki/AVL_tree) будет очень эффективным.

Другим вариантом будет использование бинарного поиска (http://en.wikipedia.org/wiki/Binary_search_algorithm). Но, опять же, это эффективно только при пропущенном списке.

Таким образом, в любом случае, если вы измените структуру данных или с помощью простого двойного связанного списка, сложность O (N) - это лучшее, чего вы можете достичь.

0 голосов
/ 16 февраля 2012

Если вам нужно вставить m элементы в отсортированный список размером n, вы можете выполнить прямую вставку со сложностью m·n или предварительно отсортировать элементы (для этого я предлагаю сортировку слиянием) иобъединить их в исходный список, который имеет сложность m·ln(m) + (n + m).

По сути, вы будете использовать специализированные версии вставки и сортировки слиянием, которые могут быть реализованы как онлайн-алгоритмы и, таким образом, хорошо подходят длясвязанный список.Имейте в виду, что реализация учебника для сортировки слиянием не работает хорошо, так как излишне повторяет список при делении его на подсписок: вам нужна немного более сложная основанная на стеке версия ...

0 голосов
/ 16 февраля 2012

Приоритетные очереди (или Пропуск списков в частности) - это то, что вы ищете.Они разрешают логарифмические вставки вместо O (n) для простых связанных списков.

Вот реализация пропуска списка в C на GitHub .

0 голосов
/ 16 февраля 2012

Связанный список обязательно требует O (N) шагов для перемещения в произвольную позицию в списке.Так что это звучит так, как будто это неправильная структура данных для вас.

Вы можете попробовать использовать отсортированный набор - std :: set в C ++ или SortedSet <> в C #.

Еслиабстракция отсортированного набора не подходит для вашей проблемы, возможно, самой простой структурой данных, которая похожа на отсортированный связанный список, является список пропусков: http://igoro.com/archive/skip-lists-are-fascinating/. Более сложными альтернативами будут деревья AVL и красно-черные деревья.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...