原地排序-更简洁的算法

在我以前的这篇文章中:原地排序与链表翻转

解决这个问题是先把链表翻转,然后再循环左移,原理是清楚了,可是稍显繁琐。这里有更简单的解法:

void rearrange(KeyVal* a, int n) {
    for (int i = 0; i < n; ++i) {
         int j;
         while ((j=a[i].key) != i) swap(a[i], a[j]);
    }
}
算法的证明:

将 swap(a[i], a[j]) 展开,即得:

         int j = a[i].key;
//       swap(a[i], a[j]); // 相当于:
         int tmpk = a[i].key;
         Val tmpv = a[i].val;
         a[i].key = a[j].key;
         a[i].val = a[j].val;
         a[j].key = tmpk;
         a[j].val = tmpv;
// 而 前面 j = a[i].key, 于是简化为:
         Val tmpv = a[i].val;
         a[i].key = a[j].key; // 从 k-cycle 中删除 j
         a[i].val = a[j].val;
         a[j].key = j; // 这个才是重点,a[j] 归位了!
         a[j].val = tmpv;

原地排序与链表翻转中提到的定理:一个全排列中,包含多个循环链表。a[i].key 可以理解为是 a[i].next,于是 a[i].key = a[j].key 就是链表操作中的: p->next = p->next->next,这是一个删除链表结点的操作!swap(a[i], a[j]) 实际上是将 a[j] 从 j 所属的那个大环状链表中删除!该算法的本质是:每次碰到一个结点,就将该结点的后继结点从循环链表中删除,删除的同时,被删除的那个结点就归位了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值