สร้างการเรียงสับเปลี่ยนทั้งหมดของรายการโดยไม่มีองค์ประกอบที่เท่ากันอยู่ติดกัน


87

เมื่อเราเรียงลำดับรายการเช่น

a = [1,2,3,3,2,2,1]
sorted(a) => [1, 1, 2, 2, 2, 3, 3]

องค์ประกอบที่เท่ากันมักจะอยู่ติดกันในรายการผลลัพธ์

ฉันจะบรรลุภารกิจที่ตรงกันข้ามได้อย่างไร - สลับรายการเพื่อไม่ให้องค์ประกอบที่เท่ากัน (หรือแทบจะไม่เป็นไปได้) อยู่ติดกัน

ตัวอย่างเช่นสำหรับรายการด้านบนหนึ่งในวิธีแก้ปัญหาที่เป็นไปได้คือ

p = [1,3,2,3,2,1,2]

เป็นทางการมากขึ้นให้รายการaสร้างการเปลี่ยนแปลงpp[i]==p[i+1]ของมันที่ช่วยลดจำนวนคู่

เนื่องจากรายการมีขนาดใหญ่การสร้างและกรองการเรียงสับเปลี่ยนทั้งหมดจึงไม่ใช่ทางเลือก

คำถามโบนัส: จะสร้างการเรียงสับเปลี่ยนทั้งหมดอย่างมีประสิทธิภาพได้อย่างไร?

นี่คือรหัสที่ฉันใช้เพื่อทดสอบการแก้ปัญหา: https://gist.github.com/gebrkn/9f550094b3d24a35aebd

UPD: การเลือกผู้ชนะที่นี่เป็นทางเลือกที่ยากเพราะหลายคนโพสต์คำตอบที่ยอดเยี่ยม @VincentvanderWeele , @David Eisenstat , @Coady , @ enrico.bacisและ@srgergจัดเตรียมฟังก์ชันที่สร้างการเปลี่ยนแปลงที่ดีที่สุดได้อย่างไร้ที่ติ @tobias_kและ David ยังตอบคำถามโบนัส (สร้างการเรียงสับเปลี่ยนทั้งหมด) คะแนนเพิ่มเติมสำหรับเดวิดสำหรับการพิสูจน์ความถูกต้อง

รหัสจาก @VincentvanderWeele ดูเหมือนจะเร็วที่สุด


1
แล้วคุณสนใจ แต่ความเท่าเทียม ? สิ่งที่ชอบ[1, 2, 1, 3, 1, 4, 1, 5]จะเหมือนกับ[1, 3, 1, 2, 1, 4, 1, 5]เกณฑ์ของคุณหรือไม่
Bakuriu

1
ไม่มีอัลกอริทึมที่ "มีประสิทธิภาพ" ใช้รายการเช่นเดียว[1, 1, 1, ..., 2, 3, 4, ..., N]กับ2Nองค์ประกอบ คุณสามารถใส่ตัวเลขn > 1ระหว่างแต่ละคู่ติดต่อกัน1เพื่อให้ได้การเรียงสับเปลี่ยนที่ดี จากนั้นคุณอนุญาตN/2องค์ประกอบและรับการเรียงสับเปลี่ยนที่ถูกต้องทั้งหมด (หมายความว่าไม่มีเลยเป็นสิ่งที่ไม่ดี แต่อาจมีมากกว่านั้น) จำนวนการเรียงสับเปลี่ยนดังกล่าวคือ O (N ^ 2) ดังนั้นคุณจึงไม่สามารถทำได้ดีกว่า O (N ^ 2) ยังดีกว่า O (N ^ 3) ของแนวทางไร้เดียงสา
Bakuriu

6
สิ่งที่สอง: @Bakuriu (1) ต้องมีความชัดเจนที่แสดงให้เห็นตัวอย่างของคุณที่จะต้องไม่มีขั้นตอนวิธีการที่มีประสิทธิภาพสำหรับคำถามโบนัส (2) การแจกแจงวิธีแก้ปัญหาที่ดีที่สุดทั้งหมดสำหรับตัวอย่างของคุณคือ O ((N / 2)!) ซึ่งแย่กว่า O (N ^ 2) มาก (กล่าวคือตัวอย่างของคุณแข็งแกร่งกว่าที่คุณคิดไว้มาก :-)
j_random_hacker

11
@msw: ฉันกำลังสร้างเว็บไซต์และมีแถวหนึ่งที่มีการบล็อกโฆษณาจากผู้ให้บริการรายอื่น ฉันต้องการจัดเรียงเพื่อไม่ให้บล็อกจากผู้ให้บริการรายเดียวกันยืนเคียงข้างกัน
georg

2
ฉันจะไม่บอกว่านี่คือ "ไม่ได้ใกล้เคียงกับรายการที่ซ้ำกัน" แต่ข้อกล่าวหาที่ซ้ำกันเป็นคำถามที่แตกต่างกันเนื่องจากมีการพิจารณาระยะห่างระหว่างองค์ประกอบที่เหมือนกัน ผู้ที่โหวตให้ปิดหลังจากความคิดเห็นของ WhyCry: โปรดให้ความสนใจมากขึ้นในอนาคต
David Eisenstat

คำตอบ:


30

สิ่งนี้เป็นไปตามแนวของรหัสเทียมที่ไม่สมบูรณ์ในขณะนี้ของ Thijser แนวคิดคือการใช้ประเภทรายการที่เหลืออยู่บ่อยที่สุดเว้นแต่จะเพิ่งถ่าย (ดูการใช้อัลกอริทึมนี้ของCoadyด้วย)

import collections
import heapq


class Sentinel:
    pass


def david_eisenstat(lst):
    counts = collections.Counter(lst)
    heap = [(-count, key) for key, count in counts.items()]
    heapq.heapify(heap)
    output = []
    last = Sentinel()
    while heap:
        minuscount1, key1 = heapq.heappop(heap)
        if key1 != last or not heap:
            last = key1
            minuscount1 += 1
        else:
            minuscount2, key2 = heapq.heappop(heap)
            last = key2
            minuscount2 += 1
            if minuscount2 != 0:
                heapq.heappush(heap, (minuscount2, key2))
        output.append(last)
        if minuscount1 != 0:
            heapq.heappush(heap, (minuscount1, key1))
    return output

หลักฐานความถูกต้อง

สำหรับรายการสองประเภทที่มีการนับ k1 และ k2 วิธีแก้ปัญหาที่เหมาะสมที่สุดจะมีข้อบกพร่อง k2 - k1 - 1 หาก k1 <k2, 0 ข้อบกพร่องถ้า k1 = k2 และ k1 - k2 - 1 บกพร่องถ้า k1> k2 กรณี = ชัดเจน คนอื่น ๆ มีความสมมาตร แต่ละอินสแตนซ์ขององค์ประกอบส่วนน้อยจะป้องกันข้อบกพร่องได้มากที่สุดสองข้อจากทั้งหมด k1 + k2 - 1 ที่เป็นไปได้

อัลกอริทึมโลภนี้ส่งคืนโซลูชันที่ดีที่สุดโดยใช้ตรรกะต่อไปนี้ เราเรียกคำนำหน้า (โซลูชันบางส่วน) ว่าปลอดภัยหากขยายไปสู่โซลูชันที่เหมาะสมที่สุด เห็นได้ชัดว่าคำนำหน้าว่างนั้นปลอดภัยและหากคำนำหน้าปลอดภัยเป็นวิธีแก้ปัญหาทั้งหมดวิธีนั้นก็เหมาะสมที่สุด เป็นการแสดงให้เห็นอย่างอุปนัยว่าแต่ละขั้นตอนที่ละโมบรักษาความปลอดภัย

วิธีเดียวที่ขั้นตอนที่ละโมบทำให้เกิดข้อบกพร่องคือหากมีเพียงรายการเดียวที่เหลืออยู่ซึ่งในกรณีนี้มีทางเดียวที่จะดำเนินการต่อและวิธีนั้นปลอดภัย มิฉะนั้นให้ P เป็นคำนำหน้า (ปลอดภัย) ก่อนขั้นตอนที่กำลังพิจารณาให้ P 'เป็นคำนำหน้าหลังจากนั้นและปล่อยให้ S เป็นทางออกที่ดีที่สุดในการขยาย P หาก S ขยาย P' ด้วยแสดงว่าเราทำเสร็จแล้ว มิฉะนั้นให้ P '= px และ S = PQ และ Q = yQ' โดยที่ x และ y คือรายการและ Q และ Q 'เป็นลำดับ

สมมติก่อนว่า P ไม่ได้ลงท้ายด้วย y ตามตัวเลือกของอัลกอริทึม x อย่างน้อยที่สุดใน Q เท่ากับ y พิจารณาสตริงย่อยสูงสุดของ Q ที่มีเพียง x และ y หากสตริงย่อยแรกมีค่า x อย่างน้อยเท่ากับ y ก็สามารถเขียนใหม่ได้โดยไม่ต้องนำข้อบกพร่องเพิ่มเติมขึ้นต้นด้วย x หากสตริงย่อยแรกมีค่า y มากกว่า x แสดงว่าสตริงย่อยอื่น ๆ บางส่วนมีค่า x มากกว่า y และเราสามารถเขียนสตริงย่อยเหล่านี้ใหม่ได้โดยไม่มีข้อบกพร่องเพิ่มเติมเพื่อให้ x ไปก่อน ในทั้งสองกรณีเราพบทางออกที่ดีที่สุด T ที่ขยาย P 'ตามความจำเป็น

สมมติว่าตอนนี้ P ลงท้ายด้วย y แก้ไข Q โดยย้าย x ที่เกิดขึ้นครั้งแรกไปด้านหน้า ในการทำเช่นนั้นเราจะแนะนำข้อบกพร่องอย่างมากที่สุดหนึ่งข้อ (โดยที่ x เคยเป็น) และกำจัดข้อบกพร่องหนึ่งข้อ (yy)

สร้างโซลูชันทั้งหมด

นี่คือคำตอบของ tobias_kพร้อมการทดสอบที่มีประสิทธิภาพเพื่อตรวจจับเมื่อตัวเลือกที่อยู่ระหว่างการพิจารณาถูก จำกัด ทั่วโลกในทางใดทางหนึ่ง เวลาทำงานแบบ asymptotic นั้นเหมาะสมที่สุดเนื่องจากค่าโสหุ้ยของการสร้างเป็นไปตามลำดับความยาวของเอาต์พุต ความล่าช้าในกรณีที่เลวร้ายที่สุดน่าเสียดายคือกำลังสอง สามารถลดลงเป็นเชิงเส้น (เหมาะสมที่สุด) ด้วยโครงสร้างข้อมูลที่ดีกว่า

from collections import Counter
from itertools import permutations
from operator import itemgetter
from random import randrange


def get_mode(count):
    return max(count.items(), key=itemgetter(1))[0]


def enum2(prefix, x, count, total, mode):
    prefix.append(x)
    count_x = count[x]
    if count_x == 1:
        del count[x]
    else:
        count[x] = count_x - 1
    yield from enum1(prefix, count, total - 1, mode)
    count[x] = count_x
    del prefix[-1]


def enum1(prefix, count, total, mode):
    if total == 0:
        yield tuple(prefix)
        return
    if count[mode] * 2 - 1 >= total and [mode] != prefix[-1:]:
        yield from enum2(prefix, mode, count, total, mode)
    else:
        defect_okay = not prefix or count[prefix[-1]] * 2 > total
        mode = get_mode(count)
        for x in list(count.keys()):
            if defect_okay or [x] != prefix[-1:]:
                yield from enum2(prefix, x, count, total, mode)


def enum(seq):
    count = Counter(seq)
    if count:
        yield from enum1([], count, sum(count.values()), get_mode(count))
    else:
        yield ()


def defects(lst):
    return sum(lst[i - 1] == lst[i] for i in range(1, len(lst)))


def test(lst):
    perms = set(permutations(lst))
    opt = min(map(defects, perms))
    slow = {perm for perm in perms if defects(perm) == opt}
    fast = set(enum(lst))
    print(lst, fast, slow)
    assert slow == fast


for r in range(10000):
    test([randrange(3) for i in range(randrange(6))])

23

รหัสเทียม:

  1. จัดเรียงรายการ
  2. วนรอบครึ่งแรกของรายการที่เรียงลำดับแล้วกรอกดัชนีคู่ทั้งหมดของรายการผลลัพธ์
  3. วนรอบครึ่งหลังของรายการที่เรียงลำดับแล้วเติมดัชนีคี่ทั้งหมดของรายการผลลัพธ์

คุณจะมีได้ก็ต่อp[i]==p[i+1]เมื่อมากกว่าครึ่งหนึ่งของอินพุตประกอบด้วยองค์ประกอบเดียวกันซึ่งในกรณีนี้ไม่มีทางเลือกอื่นนอกจากการวางองค์ประกอบเดียวกันในจุดที่ต่อเนื่องกัน (โดยหลักการของรูพิดจ์ออน)


ตามที่ระบุไว้ในความคิดเห็นแนวทางนี้อาจมีความขัดแย้งกันมากเกินไปในกรณีที่องค์ประกอบใดองค์ประกอบหนึ่งเกิดขึ้นอย่างน้อยn/2ครั้ง (หรือn/2+1สำหรับคี่nสิ่งนี้เป็นการสรุป(n+1)/2)สำหรับทั้งคู่และคี่) มีองค์ประกอบดังกล่าวมากที่สุดสององค์ประกอบและหากมีสององค์ประกอบอัลกอริทึมก็ใช้ได้ดี กรณีเดียวที่เป็นปัญหาคือเมื่อมีองค์ประกอบหนึ่งเกิดขึ้นอย่างน้อยครึ่งหนึ่งของเวลา เราสามารถแก้ปัญหานี้ได้โดยการค้นหาองค์ประกอบและจัดการกับมันก่อน

ฉันไม่รู้เกี่ยวกับ python มากพอที่จะเขียนสิ่งนี้ได้อย่างถูกต้องดังนั้นฉันจึงมีอิสระในการคัดลอกการใช้งาน OP ของเวอร์ชันก่อนหน้าจาก github:

# Sort the list
a = sorted(lst)

# Put the element occurring more than half of the times in front (if needed)
n = len(a)
m = (n + 1) // 2
for i in range(n - m + 1):
    if a[i] == a[i + m - 1]:
        a = a[i:] + a[:i]
        break

result = [None] * n

# Loop over the first half of the sorted list and fill all even indices of the result list
for i, elt in enumerate(a[:m]):
    result[2*i] = elt

# Loop over the second half of the sorted list and fill all odd indices of the result list
for i, elt in enumerate(a[m:]):
    result[2*i+1] = elt

return result

จากความเข้าใจของฉันนี่คือสิ่งที่ @jojo ทำ - ไม่เหมาะสมเสมอไป
georg

10
สิ่งนี้ล้มเหลวสำหรับ[0, 1, 1]หรือ[0, 0, 1]ขึ้นอยู่กับว่าคุณใช้ดัชนีที่อิง 0 หรือ 1
flornquake

@georg นี่เป็นแนวทางเดียวกับในคำตอบของฉัน (โปรดทราบว่า Heuster ตอบก่อนฉัน!) ในโค้ดของฉันมีการรวมขั้นตอนที่ 2. และ 3. เข้าด้วยกันดังนั้นการเพิ่มประสิทธิภาพ
jojo

3
@flornquake จับดี! มันเป็นข้อผิดพลาดแบบเดิม ๆ ที่ฉันกลัว ดังนั้นแนวทางนี้จึงไม่เหมาะสมเนื่องจากอาจมี 1 ข้อขัดแย้งมากเกินไป
Vincent van der Weele

1
@Heuster: ไฟทุกดวงเป็นสีเขียว! "0 ความผิดพลาด"
georg

10

อัลกอริทึมที่กำหนดไว้แล้วสำหรับการนำรายการที่พบบ่อยที่สุดทิ้งไว้ซึ่งไม่ใช่รายการก่อนหน้านั้นถูกต้อง นี่คือการใช้งานง่ายๆซึ่งใช้ฮีปเพื่อติดตามสิ่งที่พบบ่อยที่สุด

import collections, heapq
def nonadjacent(keys):
    heap = [(-count, key) for key, count in collections.Counter(a).items()]
    heapq.heapify(heap)
    count, key = 0, None
    while heap:
        count, key = heapq.heapreplace(heap, (count, key)) if count else heapq.heappop(heap)
        yield key
        count += 1
    for index in xrange(-count):
        yield key

>>> a = [1,2,3,3,2,2,1]
>>> list(nonadjacent(a))
[2, 1, 2, 3, 1, 2, 3]

ตัวอย่างที่ดีเกี่ยวกับการไม่เขียนอัลกอริทึมใน Python มันง่าย แต่ต้องใช้เวลา 30 นาทีในการย่อยไวยากรณ์
alex904

8

คุณสามารถสร้างทุกพีชคณิต 'ไม่ได้เรียงลำดับได้อย่างสมบูรณ์แบบ (ที่ไม่มีสององค์ประกอบที่เท่าเทียมกันในตำแหน่งที่อยู่ติดกัน) โดยใช้ขั้นตอนวิธีการย้อนรอย recursive ในความเป็นจริงความแตกต่างเพียงอย่างเดียวในการสร้างการเรียงสับเปลี่ยนทั้งหมดคือคุณติดตามตัวเลขสุดท้ายและไม่รวมวิธีแก้ปัญหาบางอย่างตาม:

def unsort(lst, last=None):
    if lst:
        for i, e in enumerate(lst):
            if e != last:
                for perm in unsort(lst[:i] + lst[i+1:], e):
                    yield [e] + perm
    else:
        yield []

โปรดทราบว่าในรูปแบบนี้ฟังก์ชันไม่มีประสิทธิภาพมากนักเนื่องจากสร้างรายการย่อยจำนวนมาก นอกจากนี้เราสามารถเร่งความเร็วได้โดยดูตัวเลขที่มีข้อ จำกัด มากที่สุดก่อน (ตัวเลขที่มีจำนวนสูงสุด) นี่เป็นเวอร์ชันที่มีประสิทธิภาพมากขึ้นโดยใช้countsตัวเลขเท่านั้น

def unsort_generator(lst, sort=False):
    counts = collections.Counter(lst)
    def unsort_inner(remaining, last=None):
        if remaining > 0:
            # most-constrained first, or sorted for pretty-printing?
            items = sorted(counts.items()) if sort else counts.most_common()
            for n, c in items:
                if n != last and c > 0:
                    counts[n] -= 1   # update counts
                    for perm in unsort_inner(remaining - 1, n):
                        yield [n] + perm
                    counts[n] += 1   # revert counts
        else:
            yield []
    return unsort_inner(len(lst))

คุณสามารถใช้สิ่งนี้เพื่อสร้างการnextเปลี่ยนแปลงที่สมบูรณ์แบบหรือการlistถือครองทั้งหมด แต่โปรดทราบว่าหากไม่มีการเปลี่ยนแปลงที่ไม่ได้เรียงลำดับอย่างสมบูรณ์เครื่องกำเนิดไฟฟ้านี้จะไม่ให้ผลลัพธ์

>>> lst = [1,2,3,3,2,2,1]
>>> next(unsort_generator(lst))
[2, 1, 2, 3, 1, 2, 3]
>>> list(unsort_generator(lst, sort=True))
[[1, 2, 1, 2, 3, 2, 3], 
 ... 36 more ...
 [3, 2, 3, 2, 1, 2, 1]]
>>> next(unsort_generator([1,1,1]))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

เพื่อหลีกเลี่ยงปัญหานี้คุณสามารถใช้สิ่งนี้ร่วมกับหนึ่งในอัลกอริทึมที่เสนอในคำตอบอื่น ๆ เป็นทางเลือก สิ่งนี้จะรับประกันได้ว่าจะส่งคืนการเปลี่ยนแปลงที่ไม่มีการเรียงลำดับอย่างสมบูรณ์หากมีหรือการประมาณที่ดีเป็นอย่างอื่น

def unsort_safe(lst):
    try:
        return next(unsort_generator(lst))
    except StopIteration:
        return unsort_fallback(lst)

สิ่งนี้ใช้หน่วยความจำ O (N ^ 2) ... สำหรับแต่ละองค์ประกอบในการเปลี่ยนแปลงที่คุณกำลังทำสำเนารายการสำหรับการเรียกซ้ำ นอกจากนี้การเรียกซ้ำจะล้มเหลวด้วยความยาว "เล็ก"
Bakuriu

@ บาคุริวเห็นด้วยนั่นคือสิ่งที่ฉันหมายถึง "ไม่ได้รับการปรับให้เหมาะสมเพื่อประสิทธิภาพ" ... แม้ว่าฉันต้องยอมรับว่าฉันไม่ได้ยกเว้นพื้นที่ O (n ^ 2) แต่คุณพูดถูก ... ฉันจะพยายามปรับปรุง .
tobias_k

O (N ^ 2) อยู่ข้างหลังเสมอเมื่อคุณมีการฟื้นคืนชีพเช่นT(n+1) = something + T(n).
Bakuriu

@tobias_k: คุณสามารถโพสต์ฟังก์ชั่นสำหรับการทดสอบเพียงครั้งเดียวได้หรือไม่?
georg

@georg แน่นอน: next(unsort2(collections.Counter(a)));-) แต่เนื่องจากอัลโกนี้สร้างความเป็นไปได้ทั้งหมดทำไมไม่ตรวจสอบทั้งหมด เพียง 38 สำหรับรายการทดสอบ 7 องค์ประกอบนั้น
tobias_k

5

ใน python คุณสามารถทำสิ่งต่อไปนี้ได้

พิจารณาว่าคุณมีรายการที่จัดเรียงไว้lคุณสามารถทำได้:

length = len(l)
odd_ind = length%2
odd_half = (length - odd_ind)/2
for i in range(odd_half)[::2]:
    my_list[i], my_list[odd_half+odd_ind+i] = my_list[odd_half+odd_ind+i], my_list[i]

สิ่งเหล่านี้เป็นเพียงการดำเนินการและควรจะค่อนข้างเร็ว ( O(N)) โปรดทราบว่าคุณจะเปลี่ยนจากl[i] == l[i+1]เป็นl[i] == l[i+2]ดังนั้นลำดับที่คุณลงท้ายด้วยอะไรก็ได้นอกจากการสุ่ม แต่จากวิธีที่ฉันเข้าใจว่าคำถามนี้ไม่ใช่การสุ่มที่คุณกำลังมองหา

แนวคิดคือการแยกรายการที่เรียงไว้ตรงกลางจากนั้นแลกเปลี่ยนองค์ประกอบอื่น ๆ ทั้งหมดในสองส่วน

สำหรับl= [1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5]สิ่งนี้นำไปสู่l = [3, 1, 4, 2, 5, 1, 3, 1, 4, 2, 5]

วิธีนี้ล้มเหลวในการกำจัดทั้งหมดl[i] == l[i + 1]ทันทีที่ความอุดมสมบูรณ์ขององค์ประกอบหนึ่งใหญ่กว่าหรือเท่ากับครึ่งหนึ่งของความยาวของรายการ

ในขณะที่ข้างต้นใช้งานได้ดีตราบเท่าที่องค์ประกอบที่พบบ่อยที่สุดมีขนาดเล็กกว่าครึ่งหนึ่งของขนาดรายการฟังก์ชันต่อไปนี้จะจัดการกับกรณี จำกัด (ปัญหาแบบ off-by-one ที่มีชื่อเสียง) โดยที่องค์ประกอบอื่น ๆ ทั้งหมดเริ่มต้นด้วย คนแรกต้องเป็นคนที่อุดมสมบูรณ์ที่สุด:

def no_adjacent(my_list):
    my_list.sort()
    length = len(my_list)
    odd_ind = length%2
    odd_half = (length - odd_ind)/2
    for i in range(odd_half)[::2]:
        my_list[i], my_list[odd_half+odd_ind+i] = my_list[odd_half+odd_ind+i], my_list[i]

    #this is just for the limit case where the abundance of the most frequent is half of the list length
    if max([my_list.count(val) for val in set(my_list)]) + 1 - odd_ind > odd_half:
        max_val = my_list[0]
        max_count = my_list.count(max_val)
        for val in set(my_list):
            if my_list.count(val) > max_count:
               max_val = val
               max_count = my_list.count(max_val)
        while max_val in my_list:
            my_list.remove(max_val)
        out = [max_val]
        max_count -= 1
        for val in my_list:
            out.append(val)
            if max_count:
                out.append(max_val)
                max_count -= 1
        if max_count:
            print 'this is not working'
            return my_list
            #raise Exception('not possible')
        return out
    else:
        return my_list

ขอบคุณ! สิ่งนี้ล้มเหลวสำหรับ[3, 2, 1, 2, 1, 3, 2](ผลตอบแทน[2, 1, 3, 1, 2, 2, 3]ควรจะเป็น(3, 2, 1, 2, 1, 3, 2)) - ดูส่วนสำคัญ
georg

@georg ขอโทษฉันไม่ดีฉันลืม a +1. ลองอีกครั้งตอนนี้
jojo

ยังคงมีปัญหา[1, 3, 3, 3, 3, 1, 1]=>[3, 1, 3, 3, 1, 3, 1]
georg

@georg ตามที่ฉันชี้ให้เห็นมันใช้งานได้ตราบเท่าที่มีอยู่น้อยกว่าครึ่งหนึ่งของความยาวของรายการซึ่งไม่ใช่กรณีในตัวอย่างนี้
jojo

@georg ดังนั้นฉันจึงเพิ่มส่วนที่จัดการกับข้อผิดพลาดทีละหนึ่ง ส่วนนี้ไม่เร็วเป็นพิเศษ (ใกล้เคียงกับอัลกอริทึมที่แนะนำโดย Thijser) แม้ว่าจะเรียกใช้เฉพาะกรณีที่หายาก
jojo

5

นี่คืออัลกอริทึมที่ดี:

  1. ก่อนอื่นให้นับจำนวนทั้งหมดว่าเกิดบ่อยเพียงใด วางคำตอบในแผนที่

  2. จัดเรียงแผนที่นี้เพื่อให้ตัวเลขที่เกิดขึ้นบ่อยที่สุดมาก่อน

  3. หมายเลขแรกของคำตอบคือตัวเลขแรกในแผนที่ที่เรียงลำดับ

  4. ปรับเปลี่ยนแผนที่โดยตอนแรกจะเล็กลง

หากคุณต้องการปรับปรุงประสิทธิภาพให้มองหาวิธีเพิ่มประสิทธิภาพของขั้นตอนการเรียงลำดับ


ใช่นี่คือสิ่งที่ @tobias_k ทำ ดูเหมือนจะทำงานได้ดี!
จอร์เจีย

@georg มันต่างกันนิดหน่อย ... ฉันใช้ตัวนับเพื่อลดความซับซ้อนของพื้นที่เท่านั้น แต่ฉันไม่ได้ทดสอบตัวเลขตามลำดับใด ๆ (คิดว่านี่อาจเป็นการเร่งความเร็วอีกครั้ง) สิ่งที่แตกต่างคือโซลูชันของฉันให้การเรียงสับเปลี่ยน 'สมบูรณ์แบบ' ทั้งหมดเสมอหากมีในขณะที่สิ่งนี้ควรให้โซลูชัน (?) ที่ดีที่สุด (สมบูรณ์แบบหรือไม่)
tobias_k

3
pseudocode นี้ไม่ถูกต้องนัก ถ้ารายการนับเป็น 5 x, 2 y, 2 z ก็จะรวม x เข้าด้วยกันโดยไม่จำเป็น ดูคำตอบของฉันสำหรับการแก้ไข
David Eisenstat

1
ตกลง เช่น [1,1,1,2,3] สิ่งนี้จะให้ผลลัพธ์เช่น [1,1,2,1,3] แทนที่จะเป็น [1,2,1,3,1]
tobias_k

ขั้นตอนที่ 3 เป็นการต่อต้าน หากตัวเลขเป็นเรื่องธรรมดา (อย่างน้อยสองรายการมากกว่าหมายเลขถัดไปที่พบบ่อยที่สุด) ขั้นตอนที่ 3 จะใช้ตัวเลขนั้นสองครั้งติดต่อกันโดยไม่มีเหตุผลใด ๆ
MSalters

5

ในการตอบคำถามโบนัส: นี่คืออัลกอริทึมที่ค้นหาการเรียงสับเปลี่ยนทั้งหมดของชุดที่ไม่มีองค์ประกอบที่อยู่ติดกันสามารถเหมือนกันได้ ฉันเชื่อว่านี่เป็นอัลกอริทึมที่มีประสิทธิภาพมากที่สุดในเชิงแนวคิด (แม้ว่าวิธีอื่น ๆ อาจเร็วกว่าในทางปฏิบัติเพราะแปลเป็นโค้ดที่ง่ายกว่า) มันไม่ได้ใช้กำลังดุร้าย แต่จะสร้างการเรียงสับเปลี่ยนที่ไม่ซ้ำกันเท่านั้นและเส้นทางที่ไม่นำไปสู่การแก้ปัญหาจะถูกตัดออกจากจุดแรกสุด

ฉันจะใช้คำว่า "องค์ประกอบมากมาย" สำหรับองค์ประกอบในชุดซึ่งเกิดขึ้นบ่อยกว่าองค์ประกอบอื่น ๆ ทั้งหมดที่รวมกันและคำว่า "ความอุดมสมบูรณ์" สำหรับจำนวนองค์ประกอบที่มีอยู่มากลบด้วยจำนวนองค์ประกอบอื่น ๆ
เช่นเซตabacไม่มีองค์ประกอบที่อุดมสมบูรณ์เซตabacaและaabcaaมีaเป็นองค์ประกอบที่อุดมสมบูรณ์และความอุดมสมบูรณ์ 1 และ 2 ตามลำดับ

  1. เริ่มต้นด้วยชุดเช่น:

aaabbcd

  1. แยกเหตุการณ์แรกออกจากการเกิดซ้ำ:

ครั้งแรก: abcd
ซ้ำ: aab

  1. ค้นหาองค์ประกอบที่มีอยู่มากมายในการทำซ้ำถ้ามีและคำนวณความอุดมสมบูรณ์:

องค์ประกอบมากมาย:
ความอุดมสมบูรณ์: 1

  1. สร้างการเรียงสับเปลี่ยนทั้งหมดของรายการแรกโดยที่จำนวนขององค์ประกอบหลังจากองค์ประกอบที่อุดมสมบูรณ์ไม่น้อยกว่าความอุดมสมบูรณ์: (ดังนั้นในตัวอย่าง "a" จะเป็นค่าสุดท้ายไม่ได้)

abcd, abdc, acbd, acdb, adbc, adcb, bacd, badc, bcad, bcda , bdac, bdca ,
cabd, cadb, cbad, cbda , cdab, cdba , dabc, dacb, abac, dbca , dcab, dcba

  1. สำหรับการเรียงสับเปลี่ยนแต่ละครั้งให้แทรกชุดของอักขระที่ซ้ำกันทีละตัวโดยปฏิบัติตามกฎเหล่านี้:

5.1. หากความอุดมสมบูรณ์ของชุดมากกว่าจำนวนองค์ประกอบหลังจากการเกิดครั้งสุดท้ายขององค์ประกอบที่มีอยู่มากมายในการเปลี่ยนแปลงจนถึงตอนนี้ให้ข้ามไปยังการเรียงสับเปลี่ยนถัดไป
เช่นเมื่อมีการเปลี่ยนแปลงจนถึงตอนนี้abcชุดที่มีองค์ประกอบมากมายaจะสามารถแทรกได้ก็ต่อเมื่อความอุดมสมบูรณ์เป็น 2 หรือน้อยกว่าเท่านั้นaaaabcก็โอเคaaaaabcไม่ใช่

5.2. เลือกองค์ประกอบจากชุดที่การเกิดครั้งสุดท้ายในการเปลี่ยนแปลงมาก่อน
เช่นเมื่อการเปลี่ยนแปลงจนถึงตอนนี้abcbaและตั้งค่าเป็นabให้เลือกb

5.3. แทรกองค์ประกอบที่เลือกอย่างน้อย 2 ตำแหน่งทางด้านขวาของการเกิดครั้งสุดท้ายในการเรียงสับเปลี่ยน
เช่นเมื่อแทรกbลงในการเปลี่ยนแปลงbabcaผลลัพธ์คือbabcbaและbabcab

5.4. เรียกคืนขั้นตอนที่ 5 ด้วยการเรียงสับเปลี่ยนผลลัพธ์และส่วนที่เหลือของชุด

EXAMPLE:
set = abcaba
firsts = abc
repeats = aab

perm3  set    select perm4  set    select perm5  set    select perm6

abc    aab    a      abac   ab     b      ababc  a      a      ababac  
                                                               ababca  
                                          abacb  a      a      abacab  
                                                               abacba  
                     abca   ab     b      abcba  a      -
                                          abcab  a      a      abcaba  
acb    aab    a      acab   ab     a      acaba  b      b      acabab  
                     acba   ab     b      acbab  a      a      acbaba  
bac    aab    b      babc   aa     a      babac  a      a      babaca  
                                          babca  a      -
                     bacb   aa     a      bacab  a      a      bacaba  
                                          bacba  a      -  
bca    aab    -
cab    aab    a      caba   ab     b      cabab  a      a      cababa  
cba    aab    -

อัลกอริทึมนี้สร้างการเรียงสับเปลี่ยนที่ไม่ซ้ำกัน หากคุณต้องการทราบจำนวนการเรียงสับเปลี่ยนทั้งหมด (ซึ่งabaจะนับสองครั้งเนื่องจากคุณสามารถสลับ a) ให้คูณจำนวนการเรียงสับเปลี่ยนที่ไม่ซ้ำกันด้วยปัจจัย:

ก = น1 ! * น2 ! * ... * N n !

โดยที่ N คือจำนวนครั้งที่เกิดขึ้นของแต่ละองค์ประกอบในชุด สำหรับชุดabcdabcabaนี้จะเป็น 4! * 3! * 2! * 1! หรือ 288 ซึ่งแสดงให้เห็นว่าอัลกอริทึมไม่มีประสิทธิภาพเพียงใดที่สร้างการเรียงสับเปลี่ยนทั้งหมดแทนที่จะเป็นเฉพาะค่าที่ไม่ซ้ำกัน ในการแสดงรายการการเรียงสับเปลี่ยนทั้งหมดในกรณีนี้เพียงแค่แสดงรายการการเรียงสับเปลี่ยนที่ไม่ซ้ำกัน 288 ครั้ง :-)

ด้านล่างนี้คือการใช้งาน (ค่อนข้างเงอะงะ) ใน Javascript ฉันสงสัยว่าภาษาอย่าง Python อาจเหมาะกว่าสำหรับสิ่งนี้ เรียกใช้ข้อมูลโค้ดเพื่อคำนวณการเรียงสับเปลี่ยนของ "abracadabra"

// FIND ALL PERMUTATONS OF A SET WHERE NO ADJACENT ELEMENTS ARE IDENTICAL
function seperatedPermutations(set) {
    var unique = 0, factor = 1, firsts = [], repeats = [], abund;

    seperateRepeats(set);
    abund = abundance(repeats);
    permutateFirsts([], firsts);
    alert("Permutations of [" + set + "]\ntotal: " + (unique * factor) + ", unique: " + unique);

    // SEPERATE REPEATED CHARACTERS AND CALCULATE TOTAL/UNIQUE RATIO
    function seperateRepeats(set) {
        for (var i = 0; i < set.length; i++) {
            var first, elem = set[i];
            if (firsts.indexOf(elem) == -1) firsts.push(elem)
            else if ((first = repeats.indexOf(elem)) == -1) {
                repeats.push(elem);
                factor *= 2;
            } else {
                repeats.splice(first, 0, elem);
                factor *= repeats.lastIndexOf(elem) - first + 2;
            }
        }
    }

    // FIND ALL PERMUTATIONS OF THE FIRSTS USING RECURSION
    function permutateFirsts(perm, set) {
        if (set.length > 0) {
            for (var i = 0; i < set.length; i++) {
                var s = set.slice();
                var e = s.splice(i, 1);
                if (e[0] == abund.elem && s.length < abund.num) continue;
                permutateFirsts(perm.concat(e), s, abund);
            }
        }
        else if (repeats.length > 0) {
            insertRepeats(perm, repeats);
        }
        else {
            document.write(perm + "<BR>");
            ++unique;
        }
    }

    // INSERT REPEATS INTO THE PERMUTATIONS USING RECURSION
    function insertRepeats(perm, set) {
        var abund = abundance(set);
        if (perm.length - perm.lastIndexOf(abund.elem) > abund.num) {
            var sel = selectElement(perm, set);
            var s = set.slice();
            var elem = s.splice(sel, 1)[0];
            for (var i = perm.lastIndexOf(elem) + 2; i <= perm.length; i++) {
                var p = perm.slice();
                p.splice(i, 0, elem);
                if (set.length == 1) {
                    document.write(p + "<BR>");
                    ++unique;
                } else {
                    insertRepeats(p, s);
                }
            }
        }
    }

    // SELECT THE ELEMENT FROM THE SET WHOSE LAST OCCURANCE IN THE PERMUTATION COMES FIRST
    function selectElement(perm, set) {
        var sel, pos, min = perm.length;
        for (var i = 0; i < set.length; i++) {
            pos = perm.lastIndexOf(set[i]);
            if (pos < min) {
                min = pos;
                sel = i;
            }
        }
        return(sel);
    }

    // FIND ABUNDANT ELEMENT AND ABUNDANCE NUMBER
    function abundance(set) {
        if (set.length == 0) return ({elem: null, num: 0});
        var elem = set[0], max = 1, num = 1;
        for (var i = 1; i < set.length; i++) {
            if (set[i] != set[i - 1]) num = 1
            else if (++num > max) {
                max = num;
                elem = set[i];
            }
        }
        return ({elem: elem, num: 2 * max - set.length});
    }
}

seperatedPermutations(["a","b","r","a","c","a","d","a","b","r","a"]);


1
ขอบคุณสำหรับสิ่งนี้! จะเห็นว่าสามารถย่อ javascript ได้เล็กน้อยหรือไม่
stt106

4

แนวคิดคือการจัดเรียงองค์ประกอบจากส่วนที่พบมากที่สุดไปยังส่วนที่พบน้อยที่สุดนำองค์ประกอบที่พบบ่อยที่สุดลดจำนวนและใส่กลับเข้าไปในรายการโดยเรียงลำดับจากมากไปหาน้อย (แต่หลีกเลี่ยงการใส่องค์ประกอบที่ใช้ครั้งสุดท้ายก่อนเพื่อป้องกันการเกิดซ้ำเมื่อทำได้) .

สามารถดำเนินการได้โดยใช้Counterและbisect:

from collections import Counter
from bisect import bisect

def unsorted(lst):
    # use elements (-count, item) so bisect will put biggest counts first
    items = [(-count, item) for item, count in Counter(lst).most_common()]
    result = []

    while items:
        count, item = items.pop(0)
        result.append(item)
        if count != -1:
            element = (count + 1, item)
            index = bisect(items, element)
            # prevent insertion in position 0 if there are other items
            items.insert(index or (1 if items else 0), element)

    return result

ตัวอย่าง

>>> print unsorted([1, 1, 1, 2, 3, 3, 2, 2, 1])
[1, 2, 1, 2, 1, 3, 1, 2, 3]

>>> print unsorted([1, 2, 3, 2, 3, 2, 2])
[2, 3, 2, 1, 2, 3, 2]

สิ่งนี้ล้มเหลวด้วยตัวอย่างเช่น[1, 1, 2, 3]ซึ่งมีวิธีแก้ปัญหาเช่น[1, 2, 1, 3].
Bakuriu

ใช่ฉันเพิ่งรู้ว่าขอโทษ
enrico.bacis

ขอบคุณ! สิ่งนี้ไม่ได้ให้ผลลัพธ์ที่ดีที่สุดเสมอไปเช่น[1, 2, 3, 2, 3, 2, 2]ผลตอบแทน[2, 3, 1, 2, 3, 2, 2](1 ความผิดพลาด) ในขณะที่อุดมคติคือ(2, 1, 2, 3, 2, 3, 2)) - ดูส่วนสำคัญ
georg

@georg จริงจับดีฉันอัปเดตโดยรักษาหลักการง่ายๆที่ใช้
enrico.bacis

@ enrico.bacis: ขอบคุณ! เวอร์ชันใหม่ทำงานได้อย่างไม่มีที่ติ ฉันได้อัปเดตส่วนสำคัญแล้ว เสียดายที่ไม่สามารถโหวตให้คุณได้อีกแล้ว
georg

2
  1. จัดเรียงรายการ
  2. สร้าง "การสุ่มที่ดีที่สุด" ของรายการโดยใช้อัลกอริทึมนี้

มันจะให้รายการขั้นต่ำจากรายการในตำแหน่งเดิม (ตามมูลค่ารายการ) ดังนั้นตัวอย่างของคุณจะทำให้ 1, 2 และ 3 อยู่ห่างจากตำแหน่งที่เรียงลำดับ


ฉันได้ลองbest_shuffleและสร้าง[1,1,1,2,3] -> [3, 1, 2, 1, 1]- ไม่เหมาะ!
georg

2

เริ่มต้นด้วยรายการเรียงลำดับของความยาว n ให้ m = n / 2 รับค่าที่ 0 แล้ว m แล้ว 1 แล้ว m + 1 ตามด้วย 2 แล้ว m + 2 และอื่น ๆ คุณจะไม่ได้ค่าที่เท่ากันในลำดับที่เท่ากัน


ขอบคุณสำหรับความคิด ฉันคิดว่านี่คือสิ่งที่ @Heuster นำมาใช้
georg

2

โปรดยกโทษให้คำตอบสไตล์ "ฉันด้วย" แต่คำตอบของ Coadyไม่สามารถทำให้เข้าใจง่ายขึ้นได้หรือไม่

from collections import Counter
from heapq import heapify, heappop, heapreplace
from itertools import repeat

def srgerg(data):
    heap = [(-freq+1, value) for value, freq in Counter(data).items()]
    heapify(heap)

    freq = 0
    while heap:
        freq, val = heapreplace(heap, (freq+1, val)) if freq else heappop(heap)
        yield val
    yield from repeat(val, -freq)

แก้ไข:นี่คือ python 2 เวอร์ชันที่ส่งคืนรายการ:

def srgergpy2(data):
    heap = [(-freq+1, value) for value, freq in Counter(data).items()]
    heapify(heap)

    freq = 0
    result = list()
    while heap:
        freq, val = heapreplace(heap, (freq+1, val)) if freq else heappop(heap)
        result.append(val)
    result.extend(repeat(val, -freq))
    return result

ใช่ดูเหมือนว่าจะทำงานได้ดี (ยกเว้นว่าฉันใช้ py2 และฟังก์ชันควรส่งคืนรายการ)
จอร์เจีย

@georg โอเคฉันได้เพิ่มเวอร์ชัน python 2 ที่ส่งคืนรายการ
srgerg

2
  1. นับจำนวนครั้งที่แต่ละค่าปรากฏ
  2. เลือกค่าตามลำดับจากบ่อยที่สุดไปหาบ่อยน้อยที่สุด
  3. เพิ่มค่าที่เลือกไปยังผลลัพธ์สุดท้ายโดยเพิ่มดัชนีครั้งละ 2 ครั้ง
  4. รีเซ็ตดัชนีเป็น 1 หากดัชนีอยู่นอกขอบเขต
from heapq import heapify, heappop
def distribute(values):
    counts = defaultdict(int)
    for value in values:
        counts[value] += 1
    counts = [(-count, key) for key, count in counts.iteritems()]
    heapify(counts)
    index = 0
    length = len(values)
    distributed = [None] * length
    while counts:
        count, value = heappop(counts)
        for _ in xrange(-count):
            distributed[index] = value
            index = index + 2 if index + 2 < length else 1
    return distributed
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.