归并排序,典型的分治思想,时间复杂度为O(nlogn)。
一、数组
递归:
void MergeSort(int* array, int low, int high, int* auxiliary_array) { if (low >= high) return; int mid = low + (high - low) / 2; MergeSort(array, low, mid, auxiliary_array); MergeSort(array, mid + 1, high, auxiliary_array); int i = low; int j = mid + 1; int index = 0; while (i <= mid && j <= high) { if (array[i] <= array[j]) { auxiliary_array[index] = array[i]; ++i; } else { auxiliary_array[index] = array[j]; ++j; } ++index; } while (i <= mid) { auxiliary_array[index] = array[i]; ++index; ++i; } while (j <= high) { auxiliary_array[index] = array[j]; ++index; ++j; } for (int count = 0; count < index; ++count) { array[low + count] = auxiliary_array[count]; } }
非递归:
void Merge(int* array, int low, int high, int* auxiliary_array) { int i = low; int mid = (low + high) / 2; int j = mid + 1; int index = 0; while (i <= mid && j <= high) { if (array[i] <= array[j]) { auxiliary_array[index] = array[i]; ++i; } else { auxiliary_array[index] = array[j]; ++j; } ++index; } while (i <= mid) { auxiliary_array[index] = array[i]; ++index; ++i; } while (j <= high) { auxiliary_array[index] = array[j]; ++index; ++j; } for (int count = 0; count < index; ++count) { array[low + count] = auxiliary_array[count]; } } void MergeSort(int* array, int length) { int* auxiliary_array = new int[8]; for(int step = 2; step < length; step *= 2) { for (int start = 0; start <= length; start += step) { int end = start + step - 1; if (end - length >= 0) end = length - 1; Merge(array, start, end, auxiliary_array); } } Merge(array, 0, length - 1, auxiliary_array); delete[] auxiliary_array; }
这里我觉得把Merge函数和MergeSort函数合并太乱了,就没写在一起。虽然这样效率会低点,但是用于学习是不错的。
二、链表
递归,原地归并:
Node* Merge(Node* start1, Node* start2) { if (nullptr == start1) return start2; if (nullptr == start2) return start1; //select the head from start1 and start2 Node* head = nullptr; if (start1->data <= start2->data) { head = start1; start1 = start1->next; } else { head = start2; start2 = start2->next; } //merge start1 part and start2 part Node* current = head; while (nullptr != start1 && nullptr != start2) { if (start1->data <= start2->data) { current->next = start1; start1 = start1->next; } else { current->next = start2; start2 = start2->next; } current = current->next; } if (nullptr != start1) { current->next = start1; } if (nullptr != start2) { current->next = start2; } return head; } Node* MergeSort(Node* head) { if (nullptr == head || nullptr == head->next) return head; Node* first = head; Node* second = first->next; while(nullptr != second && nullptr != second->next) { first = first->next; second = second->next->next; } second = first->next; first->next = nullptr; head = Merge(MergeSort(head), MergeSort(second));
return head; }
非递归,原地归并:
Node* Merge(Node* pre, Node* start1, Node* start2, Node* next) {//pre is the previous node of start1 and next is the next node of start2 if (nullptr == start1) return start2; if (nullptr == start2) return start1; //select the head from start1 and start2 Node* head = nullptr; if (start1->data <= start2->data) { head = start1; start1 = start1->next; } else { head = start2; start2 = start2->next; } //merge start1 part and start2 part Node* current = head; while (nullptr != start1 && nullptr != start2) { if (start1->data <= start2->data) { current->next = start1; start1 = start1->next; } else { current->next = start2; start2 = start2->next; } current = current->next; } if (nullptr != start1) { current->next = start1; } if (nullptr != start2) { current->next = start2; } while (nullptr != current->next) { current = current->next; } //reconnect to the origin linkeslist pre->next = head; current->next = next; return current;//return the tail of the sorted linkedlist, not the tail of origin linkedlist } Node* MergeSort(Node* head, int length) {//length is the length of the linkelist if (nullptr == head || nullptr == head->next) return head; Node* new_head = new Node; new_head->next = head; Node* pre = nullptr;//the previous node of the first part waiting for sorting Node* next = nullptr;//the next node of the second part waiting for sorting //these two noded are used to get the middle node of the linkedlist Node* first = nullptr; Node* second = nullptr; for (int step = 2; step < length; step *= 2) { pre = new_head; while (nullptr != pre && nullptr != pre->next) { int index = step / 2 + 1; first = pre->next; second = first->next; while (index < step && nullptr != second && nullptr != second->next) {//get the middle node first = first->next; second = second->next->next; ++index; } if (nullptr != second) { next = second->next; second->next = nullptr; } else { next = second; } second = first->next; first->next = nullptr; pre = Merge(pre, pre->next, second, next); } } //merge the entire linkedlist first = new_head->next; second = first->next; while (nullptr != second && nullptr != second->next) { first = first->next; second = second->next->next; } second = first->next; first->next = nullptr; Merge(new_head, new_head->next, second, nullptr); head = new_head->next; delete new_head; return head; }
本算法,虽然是原地排序,但是因为是根据步长来迭代的,需要计算链表的长度,要遍历整个数组,即多遍历了一次链表,我觉得与链表的快排相比,没有特别明显的优势。
而且我的代码写的有点乱,结构不好,如果调整一下,应该能较少20行代码,比如这篇文章——单向链表的原地归并排序实现的写法就比我好。
下面是一种更加高明的非递归归并排序:
void Swap(Node*& head1, Node*& head2) { Node* temp = head1; head1 = head2; head2 = temp; } Node* Merge(Node* start1, Node* start2) { if (nullptr == start1) return start2; if (nullptr == start2) return start1; //select the head from start1 and start2 Node* head = nullptr; if (start1->data <= start2->data) { head = start1; start1 = start1->next; } else { head = start2; start2 = start2->next; } //merge two parts of the linkedlist Node* current = head; while (nullptr != start1 && nullptr != start2) { if (start1->data <= start2->data) { current->next = start1; start1 = start1->next; } else { current->next = start2; start2 = start2->next; } current = current->next; } if (nullptr != start1) { current->next = start1; } if (nullptr != start2) { current->next = start2; } return head; } Node* MergeSort(Node* head) { if (nullptr == head || nullptr == head->next) return head; int max_merge_level = 0; Node* temp = nullptr; Node* merged_linked_list[64] = { nullptr }; Node* next = nullptr; while (nullptr != head) { int index = 0; next = head->next; head->next = temp; temp = head; head = next; //merged_linked_list[index] has a capicity of 2 ^ index //when the number of nodes merged in merged_linked_list[index] reach 2 ^ (index + 1) //these nodes will be placed in the merged_linked_list[index + 1] while (index < max_merge_level && nullptr != merged_linked_list[index]) { merged_linked_list[index] = Merge(temp, merged_linked_list[index]); temp = nullptr; Swap(merged_linked_list[index], temp); ++index; } Swap(merged_linked_list[index], temp); if (index == max_merge_level) ++max_merge_level; } int index = 0; while (index <= max_merge_level) { merged_linked_list[index + 1] = Merge(merged_linked_list[index], merged_linked_list[index + 1]); merged_linked_list[index] = nullptr; ++index; } return merged_linked_list[index]; }
merged_linked_list[64]的作用就是表示已经归并排序了的链表节点,例如merged_linked_list[1]代表有两个节点已经排序,然后此时如果又来了两个排序好的节点,那么就会将这四个节点重新排好序,并赋予merged_linked_list[2],
依次类推(循环内的代码其实就是这个意思)。
该算法与上面的算法相比,虽然需要借助辅助空间,但是辅助空间的大小是确定的,而且它有自己的两个优势:
①该算法不用在排序之前先遍历一次链表来确定长度,由此来确定最大归并步长。
②代码简洁,可能咋一看觉得算法不好懂,但是有注释的话还是清晰易懂的。