赞
踩
归并排序分为两个部分: MergeSort
和 Merge
MergeSort 是一个递归函数,在这个函数里面把待排序的数组或链表分段,直到每段的长度为1为止。
Merge 在这个函数中把分开的两段结合起来,并且在结合的过程中排序
对于一个数组用归并排序是比较方便的,而在对双向链表用归并排序时就会发现next/prev
指针指向哪里以及一些边界问题很麻烦。
下面给出两种对双向链表使用归并排序的方法
LinkedList类的定义
class LinkedList : virtual public List {
public:
typedef struct node {
int data;
struct node* next;
struct node* prev;
node(E data, struct node* next = NULL, struct node* prev = NULL): data(data), next(next), prev(prev) {}
} node;
LinkedList(); //这个函数对归并排序没什么用可以无视
~LinkedList(); //这个函数对归并排序没什么用可以无视
virtual void add(int e); //这个函数对归并排序没什么用可以无视
virtual void clear(void); //这个函数对归并排序没什么用可以无视
virtual bool contain(int e); //这个函数对归并排序没什么用可以无视
virtual bool isEmpty(void); //这个函数对归并排序没什么用可以无视
virtual void remove(int e); //这个函数对归并排序没什么用可以无视
virtual int& operator[](int index); //传入结点序号(从0开始计算)返回第index个结点的data的引用,这个函数会用到
virtual int& get(int index); //同上,然而这个函数不会被用到
virtual int indexOf(int element); //这个函数对归并排序没什么用可以无视
virtual void sort(void); //排序(归并排序)
virtual int size(void); //这个函数对归并排序没什么用可以无视
private:
node* head; //链表头指针
node* tail; //链表尾指针
int _size; //结点总个数
};
//下面是virtual int& operator[](int index);的定义
int& LinkedList::operator[](int index) {
node *p = head;
for (int i = 0; i < index; i++) {
p = p->next;
}
return p->data;
}
inline void MergeSort(LinkedList *li, LinkedList::node *low, LinkedList::node *high,
LinkedList::node *head, int i) {
// li就是待排序的链表,head是头指针,在Merge函数里要用到这两个东西
//i=high-low,比如high是第3个结点,low是第0个结点,那么i就是3-0=3,i用来找到low与high的中间结点
LinkedList::node *mid = low, *p = low;
if (low != high) {
int m = i/2, n = i/2; //m和n是i的1/2
while (m--) {
mid = mid->next; //经过m次循环,mid指向了low到high中间位置的结点
}
MergeSort(li, low, mid, head, n); //继续分离左半部分,此时的low就是上面的low,而high是上面的mid,high(即mid)-low = i/2 = n
MergeSort(li, mid->next, high, head, i-n-1); //分离右半部分,此时的low是mid->next, high就是上面的high,high-low(即mid->next) = i-n-1
Merge(li, low, mid, high, i+1, head); //合并两部分
}
}
inline void Merge(LinkedList *li, LinkedList::node *low, LinkedList::node *mid, LinkedList::node *high,
int size, LinkedList::node *head) {
//size是从low到high的结点总个数,包括low和high,所以上面传入的是i+1
LinkedList::node *p = low, *q = mid->next, *r = head; //r用于找到low所指向的结点的序号
int a[size], i = 0, j = 0, k; //建一个数组用于记录排序后的数字顺序
while (p != mid->next && q != high->next) {
if (p->data <= q->data) {
a[i++] = p->data;
p = p->next;
} else {
a[i++] = q->data;
q = q->next;
}
} //比较两部分的数字的大小,把小的先输出到数组a中
while (p != mid->next) {
a[i++] = p->data;
p = p->next;
} //若第一部分有剩余则全部输出到数组a中
while (q != high->next) {
a[i++] = q->data;
q = q->next;
} //若第二部分有剩余则全部输出到数组a中
while (r != low) {
r = r->next;
j++;
} 找到low指向的结点的序号,用j记录
i = 0;
for (k = j; k < j+size; k++) {
(*li)[k] = a[i++];
} //把数组a中数字按顺序复制到low到high这个区间里
}
/*接下来在sort中只要写一句就够了*/
void LinkedList::sort(void) {
MergeSort(this, head, tail, head, _size-1);
}
这个方法优点在于思考起来比较方便,而且不用改变next/prev
指向的地址,缺点在于比较耗时
for (k = j; k < j+size; k++) {
(*li)[k] = a[i++];
} //把数组a中数字按顺序复制到low到high这个区间里
//下面是virtual int& operator[](int index);的定义
int& LinkedList::operator[](int index) {
node *p = head;
for (int i = 0; i < index; i++) {
p = p->next;
}
return p->data;
}
可以发现每用一次(*li)[k]
,都会从头搜索一次,当size很大的时候会消耗很多时间,下面提一种优化的方法(虽然只能优化那么一点点)
/*如果index大于size/2,那么就从尾部开始搜索*/
LinkedList::int& LinkedList::operator[](int index) {
node *p;
if (index < _size/2) {
p = head;
for (int i = 0; i < index; i++) {
p = p->next;
}
} else {
p = tail;
int k = _size-index-1;
for (int i = 0; i < k; i++) {
p = p->prev;
}
}
return p->data;
}
void LinkedList::sort(void) {
if (this->size() > 1) {
node* fast = this->head;
node* slow = this->head;
LinkedList li_left;
LinkedList li_right;
li_left.head = this->head;
while (fast != NULL && fast->next != NULL) {
li_left._size++;
fast = fast->next->next;
slow = slow->next;
} //fast一次走两步,slow一次走一步,当fast到底时,slow就处于中间位置
//从head到slow->prev为一段
li_left.tail = slow->prev;
li_left.tail->next = NULL;
//从slow到tail为另一段
li_right.head = slow;
li_right.head->prev = NULL;
li_right.tail = this->tail;
li_right._size = this->_size - li_left._size;
this->head = NULL;
this->tail = NULL;
//继续对两段进行分割、排序
li_left.sort();
li_right.sort();
node* pointer_left = li_left.head;
node* pointer_right = li_right.head;
node* pointer_head = NULL;
node* pointer_tail = NULL;
while (pointer_left != NULL && pointer_right != NULL) {
node* temp;
if (pointer_left->data <= pointer_right->data) {
temp = pointer_left;
pointer_left = pointer_left->next;
} else {
temp = pointer_right;
pointer_right = pointer_right->next;
} //temp指向pointer_left与pointer_right中数值较小的结点
if (pointer_head == NULL) {
pointer_head = pointer_tail = temp;
} else {
pointer_tail->next = temp;
temp->prev = pointer_tail;
pointer_tail = temp;
} //把temp接到链表中
pointer_head->prev = NULL;
pointer_tail->next = NULL;
}
while (pointer_left != NULL) {
pointer_tail->next = pointer_left;
pointer_left->prev = pointer_tail;
pointer_tail = pointer_left;
pointer_left = pointer_left->next;
}
while (pointer_right != NULL) {
pointer_tail->next = pointer_right;
pointer_right->prev = pointer_tail;
pointer_tail = pointer_right;
pointer_right = pointer_right->next;
}
this->head = pointer_head;
this->tail = pointer_tail;
//注意要把left,right的head,tail指针清空,否则由于是浅拷贝,退出函数栈的时候会把this的空间都释放。
li_left.head = li_left.tail = NULL;
li_right.head = li_right.tail = NULL;
}
}
这种方法比较高效,可是需要思考的很严密,一不小心就会出错。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。