双向链表的归并排序

双向链表的归并排序

归并排序分为两个部分: MergeSortMerge

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;
}

方法一:在Merge函数中使用数组

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;
}

2.方法二:改变指针指向

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;
  }
}

这种方法比较高效,可是需要思考的很严密,一不小心就会出错。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值