найдовший список слів з відповідними початковою та кінцевою літерами


11

Мій друг дав мені проблему, за яку він каже, що це легко, але я не можу розробити хороший алгоритм, який би використовував для цього.

Вам вводять 100 випадкових англійських слів. Ви повинні знайти найдовший рядок слів, де остання літера в одному слові відповідає першій букві в наступному слові. Ви можете використовувати кожне слово лише один раз.

Наприклад, якби вам дали слова "кішка", "собака", "те", найдовша рядок, яку ви могли б зробити, була б "кішка -> та". Якби вам дали слова "миша", "лось", "єдиноріг", найдовша рядок, яку ви могли б скласти, буде лише одним словом (оскільки жодне з цих слів не посилається). Якби вам дали слова "птах", "блюдо", "гарбуз", найдовша строка, яку ви могли б скласти, була б "гарба -> птах -> блюдо" (або "блюдо -> габ -> птах" або "птах - > блюдо -> габ ").

Я придумав ідею моделювати це як спрямований циклічний графік. Кожен вузол буде просто словом; вершини йдуть до кожного слова / вузла, який починався з літери, якою закінчується це слово.

+-------+         \ +------+
|  cat  |-----------| that |
+-------+         / +------+
    |                  |
   \|/                 |
+-------+ /            |
|  the  |--------------+
+-------+ \

Ця проблема виглядає як найдовший пошук шляху , який є NP-Hard.

Чи є кращий спосіб це зробити? Або навіть якийсь алгоритм наближення, який можна було б використати? Або якимось чином використовувати якості англійської мови, щоб зменшити простір пошуку?


4
Зі 100 слів ви отримуєте (принаймні) 100! = 9,332622e + 157 комбінацій. Удачі в цьому, я думаю, що твій друг тягне за тебе ногу, кажучи, що це легко.
Мартін Вікман

1
Але кількість можливих комбінацій значно менше, ніж це, оскільки в середньому одне слово пов'язане лише приблизно з 6 або 7 іншими словами.
Abe Tool

2
Ви вірні, що це саме довгий пошук шляху. Я думаю, що твій друг помиляється. Однак вичерпний пошук не важко кодувати і може не запускатись так довго.
Кевін Клайн

4
Для розваги я зашифрував грубі сили вичерпного пошуку (як вказував @kevincline) у Рубі ( gist.github.com/anonymous/6225361 ). За 100 слів це зайняло лише ~ 96 секунд ( gist.github.com/anonymous/6225364 ). І це був дуже неефективний, неоптимізований, інтерпретований, швидкий та брудний сценарій. Тож із лише 100 слів навіть повільна версія грубої сили працює за розумну кількість часу. Мій код насправді не створює ациклічний графік, а потім здійснює пошук по ньому, він просто рекурсивно будує кожен можливий шлях, починаючи з кожного слова, і відслідковує найдовші.
Бен Лі

3
Проблема говорить про те, що є 100 слів. Я думаю, це означає, що ви можете застосувати рішення щодо динамічного програмування, про яке йдеться у статті, яку ви посилаєтесь.
Жульєн Герто

Відповіді:


5

Я думаю, це пов’язано з проблемою, яку ви найдовший шлях (LP), про яку ви згадали, але трохи іншою. Основна відмінність полягає в тому, що проблема LP має більш високий ступінь підключення, ніж те, що запропонована вами проблема. Обмеживши свої з'єднання останньою та першою літерами, ви видалите велику кількість потенційних комбінацій.

Ось як я рекомендую вирішити цю проблему:

  1. Для кожного слова у списку підраховуйте можливі з'єднання та з'єднання.
  2. Відмовтеся від будь-яких слів, у яких 0 входів і 0 виходів.
  3. Визначте початковий набір "початкових слів" з найменшою кількістю входів і виходів, а вихідні повинні бути більше 0.
  4. Кожне початкове слово отримує власну робочу копію кількості підключень входів / виходів. Це утворює голову ланцюга.
  5. Для кожного ланцюжка визначте список "наступних слів" на основі:
    • остання літера початкового або попереднього слова
    • найменша кількість підключень входів і виходів (знову ж таки, вихідні повинні бути більше 0)
  6. Для кожного next wordповторюйте крок 5, поки ланцюг не припиняється.

Майте на увазі, що:

  • Вам потрібно буде відслідковувати довжину ланцюгів і мати якийсь глобальний механізм для визначення найдовшого ланцюга.

  • Вам також потрібно буде видалити кожне слово з робочої копії підрахунків підключень, щоб уникнути рекурсивного циклу.

  • У якийсь момент ваш ланцюг припиниться, і вам доведеться вибрати слово з підрахунком 0.

  • Можливо, вам доведеться перерахувати внески / виходи, оскільки слова видаляються з робочих списків. На перший погляд, я не думаю, що це буде потрібно, оскільки загальний набір буде порівняно невеликим. Якщо ви зменшили масштаб до 1000 слів, то статичні підрахунки можуть уповільнити алгоритм зближення.

Я начебто бачив це як проблему з упаковкою. Для мене з'єднання в і з ототожнюють форму, яку потрібно упакувати. Чим нижче з'єднання, тим більш дивна форма. Чим більше дивна форма, тим швидше я хочу упакувати її, як я відчув, що зменшуються шанси на можливість упакування непарної форми, чим пізніше я потрапив у ланцюжок.

Як приклад:

{dog, gopher, alpha, cube, elegant, this, that, bart}

dog     0, 1
gopher  1, 0
alpha   0, 0
cube    0, 1
elegant 1, 2
this    3, 0
that    2, 1
bart    0, 2

//alpha is dropped with 0 in and 0 out.
//two candidates found: dog, cube

//chain 1
dog => gopher
//chain 2
cube => elegant => that => this

//Note 1: the following chain won't occur due to selection rules
//that takes priority over this because of output count
cube => elegant => this

//Note 2: this chain won't occur either due to selection rules
bart => that => this

2
Чи є гарантія, що цей алгоритм завжди знайде найдовший шлях? Вгорі голови я не можу придумати зустрічний приклад, але це здається, що це може бути рішенням типу "локальний максимум".
Бен Лі

@BenLee - я інженер програмного забезпечення; Я ніколи не гарантую свій код. :-) Хоча якщо серйозно, я не знаю відповіді на ваше запитання. Моя теорія множин та навички математичного доказування слабкі, м'яко кажучи, тому я не маю жодного способу, крім емпіричного оцінювання, щоб підтвердити свій алгоритм. Я не впевнений, що ця проблема справді непроста, але я також не можу підтвердити цю заяву. Якщо це не важко для NP, тоді повинен бути засіб перевірки алгоритму.

2
Як щодо списку слів на кшталт цього: "собака, ховрах, булочка, черниця, полудень, нуб". Алгоритм неправильно обрав би найдовший список як "собака -> ховрах", коли він насправді є будь-яким поєднанням "булочка, черниця, полудень, нуб".
Abe Tool

1
@AbeTool - хороший приклад є. Я б додав ще одну ітерацію (або дві), щоб потім було можливим поєднання "найменший вхід> = 1" і "найнижчий вихід> = 1".

2
Я не думаю, що це вирішить проблему у всіх випадках. Я думаю, що це відноситься до рішення "локального максимуму".
Abe Tool

3

Якщо ви зробите матрицю 26X26 для відображення спрямованого графа вершин як кожного алфавіту, а слова як ребра. Наприклад, слово - APPLE з'єднати вершини A і E з ребром, спрямованим від A до E. Тепер проблема зводиться до пошуку найбільшого ейлерового сліду (шлях, який включає в себе максимальну кількість ребер, відвідування кожного краю один раз з можливим повторенням вершин) у графі. Одним з алгоритмів O (E) було б починати випадковим чином з пари вершин. Знайдіть шлях між ними. Тоді продовжуйте розслабляти шлях, поки це можливо.

update @ GlenH7 Нещодавно я вирішив подібне запитання на www.hackerearth / jda, щодо найкращого рішення були відносні оцінки, і я набрав найвищі бали з наступним підходом-

Дано список слів. Знайдіть найдовший ланцюг, який може бути сформований ними. Ланцюжок дійсний, якщо кожне слово починається з букви *, що закінчується в кінці останнього слова.

Приближення =

1) складіть графік алфавітів як вершини, а слова як ребра. Замість використання декількох ребер використовуйте один із вагою, рівним кількості ребер.

2) знайти сильно пов'язаний компонент графа з максимальними ребрами. Тимчасово відкиньте інші краї.

3) Для кожної вершини зробіть її інгредієнт рівним її завищенню.

4) Тепер у графіку існує їхня ейлерова схема. Знайди це.

5) Тепер у графі, що залишився (wrt оригінальний графік, знайдіть найдовший слід із першою вершиною у вибраному сильно з'єднаному компоненті. Я думаю, що це NP важко.

6) Включіть вищевказаний слід у ланцюг Елеріана, що перетворює ейлерову ланцюг у слід.

Чому - я приймаю, що це питання, ймовірно, NP важке (здогадуйтесь, не математично кажучи). Але вищезазначений підхід найкраще працює, коли існує довгий список (1000+) рівномірно розподілених слів (тобто не призначений бути туалетом для вищевказаного підходу). Припустимо, що після перетворення заданого списку у згаданий вище графік він, на щастя, виявляється ейлеровим графіком (див. Http://en.wikipedia.org/wiki/Eulerian_path щодо умов), то без жодного сумніву ми можемо сказати цю відповідь вище питання P і є насправді еулеровим шляхом у графіку (див. http://www.graph-magics.com/articles/euler.php для дуже простого підходу, щоб зробити це, і переконатися в цьому, щоб переконатися, що ваш графік має одномісний http://www.geeksforgeeks.org/strongly-connected-components/і якщо не тимчасово очистити інші невеликі scc, тому що ейлеровий шлях існує для однієї scc). Таким чином, для не щасливих випадків (які майже у всіх випадках) я намагаюся перетворити їх на випадкові випадки (тобто умови ейлерового сліду виконуються). Як це зробити? Я намагався зробити пошук глибини для невідповідних ребер (сукупність ребер в шляху, що починається від вершини з перевищенням величини, ніж невирішене, і закінчується у вершині з величиною, що перевищує невирішеність). Збільшення пошуку по глибині означає, що спершу я шукав весь такий набір одного краю шляху, ніж два ребра в шляху і так далі. На перший погляд може здатися, що пошук по глибині зайняв би O (вузли ^ i), таким чином, загальна часова складність O (вузли + вузли ^ 2 + вузли ^ 3 + ....), поки це не вдалий випадок. Але амортизований аналіз сподобається, що це O (ребра). Як тільки це скорочено, щасливий випадок знайдіть ейлерову ланцюг.

До цього це був увесь полином. Це дало б майже найкраще рішення. Але для подальшого збільшення вашого рішення (ідеальне рішення - важке NP), спробуйте якийсь жадібний підхід у графі, що залишився, щоб знайти довгий слід, який дивився однією з вершин обраної області. Тепер додайте це до вище знайденого еулерового сліду для подальшого його збільшення.


@ GlenH7 Нещодавно я вирішив подібне запитання на www.hackerearth / jda, там було відносне оцінювання щодо найкращого рішення, і я набрав найвищі бали при такому підході
vishfrnds

0

Ідея:

Спочатку створіть дві карти (хеші), скажімо, S і E, від букв алфавіту до слів; перший, S, відображає початкові літери до слів, другий, E, робить те ж саме із закінченнями літер.

Наприклад, якщо словник складається з:

птах, блюдо, собака, габ

ми маємо:

S:

a -> [ ]
b -> [ bird ]
c -> [ ]
d -> [ dish, dog ]
...
h -> [ harb ]
...

і,

E:

a -> [ ]
b -> [ harb ]
c -> [ ]
d -> [ bird ]
...
g -> [ dog ]
h -> [ dish ]
...

Далі, використовуючи S і E для швидкого пошуку, створіть ліс (набір дерев), такого ж розміру, як словник, з корінням у кожному слові, і не дозволяючи слову з’являтися більше одного разу на дереві - кешуйте глибини дерев під час їх побудови:

bird (depth: 2)
   dish
      harb
   dog

dish (depth: 3)
   harb
      bird
         dog

dog (depth: 0)

harb (depth: 2)
   bird
      dish
      dog

Нарешті, перегляньте ліс і знайдіть дерево (дерева) найбільшої глибини.

Розчин (и) буде знаходитись на осі нащадків цих дерев.

Наприклад,

dish / harb / bird / dog

вище.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.