กำลังค้นหาลำดับจำนวนเต็ม


14

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

ใช้ลำดับของจำนวนเต็ม A เช่น (1 2 3 4)

ใช้ลำดับของจำนวนเต็มและทดสอบว่ามีรายการใดตรงกับ A ดังกล่าวหรือไม่

  1. A มีจำนวนเต็มทั้งหมดในลำดับการทดสอบ
  2. การเรียงลำดับของจำนวนเต็มในลำดับการทดสอบจะเหมือนกันใน A
  3. เราไม่สนใจจำนวนเต็มใน A ที่ไม่อยู่ในลำดับการทดสอบ
  4. เราต้องการลำดับการทดสอบที่ตรงกันทั้งหมดไม่ใช่แค่ลำดับแรก

ตัวอย่าง

A = (1 2 3 4)
B = (1 3)
C = (1 3 4)
D = (3 1)
E = (1 2 5)

B ตรงกับ A

C ตรงกับ A

D ไม่ตรงกับ A เนื่องจากการสั่งซื้อนั้นแตกต่างกัน

E ไม่ตรงกับ A เนื่องจากมีจำนวนเต็มไม่อยู่ใน A

ฉันหวังว่าคำอธิบายนี้ชัดเจนเพียงพอ สิ่งที่ดีที่สุดที่ฉันสามารถทำได้คือสร้างโครงสร้างของลำดับการทดสอบแล้ววนซ้ำ A. ความจำเป็นที่จะสามารถข้ามจำนวนเต็มนำไปสู่เส้นทางการค้นหาที่ไม่สำเร็จจำนวนมาก

ขอบคุณ

อ่านคำแนะนำบางอย่างฉันรู้สึกว่าฉันต้องชี้แจงประเด็นสองสามข้อที่ฉันคลุมเครือเกินไป

  1. อนุญาตให้ใช้ตัวเลขซ้ำได้ในความเป็นจริงสิ่งนี้ค่อนข้างสำคัญเนื่องจากอนุญาตให้ลำดับการทดสอบเดียวเพื่อจับคู่ A นั้นมีหลายวิธี

    A = (1234356), B = (236) การแข่งขันอาจเป็น -23 --- 6 หรือ -2--3-6

  2. ฉันคาดว่าจะมีลำดับของการทดสอบจำนวนมากในพันอย่างน้อยและลำดับ A จะมีความยาวสูงสุด 20 อาจดังนั้นเพียงแค่พยายามจับคู่แต่ละชุดทดสอบทีละคนโดย iterating จะไม่มีประสิทธิภาพมาก

ขออภัยหากยังไม่ชัดเจน


4
คุณฟังดูราวกับว่าคุณแค่ต้องการตรวจจับชุดข้อมูล ( en.wikipedia.org/wiki/Subsequence ) มันคืออะไร จากนั้นลองค้นหา "ขั้นตอนวิธีการเรียงลำดับ"
Kilian Foth

สุจริตลำดับสองสามพันลำดับที่มีความยาวสูงสุด <= 20 ไม่ได้ส่งเสียงจำนวนมากสำหรับฉัน วิธีเดรัจฉานบังคับง่าย ๆ ควรทำการหลอกลวง หรือคุณมีลำดับ "A" หลายพันรายการแต่ละบทเพื่อทดสอบเรียงลำดับที่เป็นไปได้หลายพันรายการ
Doc Brown

มีกระแสลำดับต่อเนื่อง A แต่มีความเป็นอิสระจากกัน อย่างไรก็ตามความล่าช้าในการประมวลผลจะล่าช้าโดยตรงทั้งหมดดังนั้นความเร็วจึงมีความสำคัญ
David Gibson

1
ตัวอักษรของคุณใหญ่แค่ไหน? คุณมีจำนวนเต็มตามอำเภอใจจริง ๆ หรือมีช่วงของค่าที่ จำกัด ซึ่งเราสามารถทำการคำนวณล่วงหน้าได้หรือไม่?
แฟรงก์

ช่วงที่เป็นไปได้ของจำนวนเต็มอยู่ใน 100,000s
David Gibson

คำตอบ:


18

อืมฉันสามารถนึกถึงอัลกอริธึมที่เป็นไปได้สองอย่าง: การสแกนเชิงเส้นผ่านลำดับAหรือสร้างพจนานุกรมด้วยการค้นหาดัชนีเวลาคงที่

หากคุณกำลังทดสอบการเรียงลำดับที่อาจเกิดขึ้นBกับลำดับA ที่มีขนาดใหญ่กว่านี้ฉันขอแนะนำให้คุณใช้ตัวแปรกับพจนานุกรม

การสแกนเชิงเส้น

ลักษณะ

เรายังคงเคอร์เซอร์สำหรับลำดับ จากนั้นเราก็ย้ำผ่านรายการทั้งหมดใน subsequence B สำหรับแต่ละรายการเราเลื่อนเคอร์เซอร์ไปข้างหน้าในAจนกระทั่งพบรายการที่ตรงกัน หากไม่พบรายการที่ตรงกันBนั้นจะไม่เป็นลำดับ

สิ่งนี้จะทำงานในO (seq.size)เสมอ

pseudocode

สไตล์ที่จำเป็น:

def subsequence? seq, subseq:
  i = 0
  for item in subseq:
    i++ while i < seq.size and item != seq[i]
    return false if i == seq.size
  return true

สไตล์การทำงาน:

let rec subsequence? = function
| _ [] -> true
| [] _ -> false
| cursor::seq item::subseq ->
  if   cursor = item
  then subsequence? seq subseq
  else subsequence? seq item::subseq

ตัวอย่างการนำไปใช้ (Perl):

use strict; use warnings; use signatures; use Test::More;

sub is_subsequence_i ($seq, $subseq) {
  my $i = 0;
  for my $item (@$subseq) {
    $i++ while $i < @$seq and $item != $seq->[$i];
    return 0 if $i == @$seq;
  }
  return 1;
}

sub is_subsequence_f ($seq, $subseq) {
  return 1 if @$subseq == 0;
  return 0 if @$seq == 0;
  my ($cursor, @seq) = @$seq;
  my ($item, @subseq) = @$subseq;
  return is_subsequence_f(\@seq, $cursor == $item ? \@subseq : $subseq);
}

my $A = [1, 2, 3, 4];
my $B = [1, 3];
my $C = [1, 3, 4];
my $D = [3, 1];
my $E = [1, 2, 5];

for my $is_subsequence (\&is_subsequence_i, \&is_subsequence_f) {
  ok $is_subsequence->($A, $B), 'B in A';
  ok $is_subsequence->($A, $C), 'C in A';
  ok ! $is_subsequence->($A, $D), 'D not in A';
  ok ! $is_subsequence->($A, $E), 'E not in A';
  ok $is_subsequence->([1, 2, 3, 4, 3, 5, 6], [2, 3, 6]), 'multiple nums';
}

done_testing;

ค้นหาพจนานุกรม

ลักษณะ

เราแมปรายการของลำดับAกับดัชนีของพวกเขา จากนั้นเราจะค้นหาดัชนีที่เหมาะสมสำหรับแต่ละรายการในBข้ามดัชนีที่มีขนาดเล็กและเลือกดัชนีที่เล็กที่สุดเท่าที่จะเป็นไปได้ เมื่อไม่พบดัชนีดังนั้นBจะไม่เป็นลำดับ

ทำงานในสิ่งที่ต้องการO (subseq.size · k)ที่kseqอธิบายวิธีจำนวนตัวเลขที่ซ้ำกันที่มีอยู่ใน บวกค่าใช้จ่ายO (seq.size)

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

pseudocode:

สไตล์ที่จำเป็น:

# preparing the lookup table
dict = {}
for i, x in seq:
  if exists dict[x]:
    dict[x].append(i)
  else:
    dict[x] = [i]

def subsequence? subseq:
  min_index = -1
  for x in subseq:
    if indices = dict[x]:
      suitable_indices = indices.filter(_ > min_index)
      return false if suitable_indices.empty?
      min_index = suitable_indices[0]
    else:
      return false
  return true

สไตล์การทำงาน:

let subsequence? subseq =
  let rec subseq-loop = function
  | [] _ -> true
  | x::subseq min-index ->
    match (map (filter (_ > min-index)) data[x])
    | None -> false
    | Some([]) -> false
    | Some(new-min::_) -> subseq-loop subseq new-min
  in
    subseq-loop subseq -1

ตัวอย่างการนำไปใช้ (Perl):

use strict; use warnings; use signatures; use Test::More;

sub build_dict ($seq) {
  my %dict;
  while (my ($i, $x) = each @$seq) {
    push @{ $dict{$x} }, $i;
  }
  return \%dict;
}

sub is_subsequence_i ($seq, $subseq) {
  my $min_index = -1;
  my $dict = build_dict($seq);
  for my $x (@$subseq) {
    my $indices = $dict->{$x} or return 0;
    ($min_index) = grep { $_ > $min_index } @$indices or return 0;
  }
  return 1;
}

sub is_subsequence_f ($seq, $subseq) {
  my $dict = build_dict($seq);
  use feature 'current_sub';
  return sub ($subseq, $min_index) {
    return 1 if @$subseq == 0;
    my ($x, @subseq) = @$subseq;
    my ($new_min) = grep { $_ > $min_index } @{ $dict->{$x} // [] } or return 0;
    __SUB__->(\@subseq, $new_min);
  }->($subseq, -1);
}

my $A = [1, 2, 3, 4];
my $B = [1, 3];
my $C = [1, 3, 4];
my $D = [3, 1];
my $E = [1, 2, 5];

for my $is_subsequence (\&is_subsequence_i, \&is_subsequence_f) {
  ok $is_subsequence->($A, $B), 'B in A';
  ok $is_subsequence->($A, $C), 'C in A';
  ok ! $is_subsequence->($A, $D), 'D not in A';
  ok ! $is_subsequence->($A, $E), 'E not in A';
  ok $is_subsequence->([1, 2, 3, 4, 3, 5, 6], [2, 3, 6]), 'multiple nums';
}

done_testing;

ตัวแปรการค้นหาพจนานุกรม: การเข้ารหัสเป็นเครื่องสถานะ จำกัด

ลักษณะ

เราสามารถลดความซับซ้อนของอัลกอริทึมลงไปที่O (subseq.size) ได้หากเราแลกเปลี่ยนในหน่วยความจำมากขึ้น แทนที่จะทำแผนที่องค์ประกอบไปยังดัชนีของพวกเขาเราสร้างกราฟที่แต่ละโหนดแสดงองค์ประกอบที่ดัชนี ขอบแสดงเปลี่ยนไปได้เช่นลำดับจะมีขอบa, b, a a@1 → b@2, a@1 → a@3, b@2 → a@3กราฟนี้เทียบเท่ากับเครื่องสถานะ จำกัด

ในระหว่างการค้นหาเรารักษาเคอร์เซอร์ซึ่งเริ่มต้นเป็นโหนดแรกของทรี จากนั้นเราเดินขอบสำหรับองค์ประกอบในรายการย่อยแต่ละB หากไม่มีขอบดังกล่าวแสดงว่าBไม่ใช่รายการย่อย หากหลังจากองค์ประกอบทั้งหมดเคอร์เซอร์มีโหนดที่ถูกต้องแล้วBเป็นรายการย่อย

pseudocode

สไตล์ที่จำเป็น:

# preparing the graph
graph = {}
for x in seq.reverse:
  next_graph = graph.clone
  next_graph[x] = graph
  graph = next_graph

def subseq? subseq:
  cursor = graph
  for x in subseq:
    cursor = graph[x]
    return false if graph == null
  return true

สไตล์การทำงาน:

let subseq? subseq =
  let rec subseq-loop = function
  | [] _ -> true
  | x::subseq graph -> match (graph[x])
    | None -> false
    | Some(next-graph) -> subseq-loop subseq next-graph
  in
    subseq-loop subseq graph

ตัวอย่างการนำไปใช้ (Perl):

use strict; use warnings; use signatures; use Test::More;

sub build_graph ($seq) {
  my $graph = {};
  for (reverse @$seq) {
    $graph = { %$graph, $_ => $graph };
  }
  return $graph;
}

sub is_subsequence_i ($seq, $subseq) {
  my $cursor = build_graph($seq);
  for my $x (@$subseq) {
    $cursor = $cursor->{$x} or return 0;
  }
  return 1;
}

sub is_subsequence_f ($seq, $subseq) {
  my $graph = build_graph($seq);
  use feature 'current_sub';
  return sub ($subseq, $graph) {
    return 1 if @$subseq == 0;
    my ($x, @subseq) = @$subseq;
    my $next_graph = $graph->{$x} or return 0;
    __SUB__->(\@subseq, $next_graph);
  }->($subseq, $graph);
}

my $A = [1, 2, 3, 4];
my $B = [1, 3];
my $C = [1, 3, 4];
my $D = [3, 1];
my $E = [1, 2, 5];

for my $is_subsequence (\&is_subsequence_i, \&is_subsequence_f) {
  ok $is_subsequence->($A, $B), 'B in A';
  ok $is_subsequence->($A, $C), 'C in A';
  ok ! $is_subsequence->($A, $D), 'D not in A';
  ok ! $is_subsequence->($A, $E), 'E not in A';
  ok $is_subsequence->([1, 2, 3, 4, 3, 5, 6], [2, 3, 6]), 'multiple nums';
}

done_testing;

นอกจากนี้คุณได้แหย่ว่าวิธีการstudyทำงานและหากอัลกอริทึมที่ใช้อาจมีการใช้งานจริงที่นี่?

1
@MichaelT ฉันไม่แน่ใจว่าฉันเข้าใจ ... ฉันเป็นนักศึกษาปริญญาตรี แต่ฉันยังไม่ได้ค้นพบวิธีการศึกษา </joke> หากคุณกำลังพูดถึงฟังก์ชั่น Perl builtin: มันไม่ต้องใช้เลยในปัจจุบัน การใช้งานในปัจจุบันเป็นเพียงแค่โหลของความเข้ากันได้ย้อนหลัง เอ็นจิ้น regex ใช้การวิเคราะห์พฤติกรรมโดยตรงเช่นการค้นหาสตริงคงที่ก่อนจับคู่รูปแบบขนาดตัวแปร studyก่อนหน้านี้ได้สร้างตารางการค้นหาแบบตัวอักษรต่อตำแหน่งไม่เหมือนกับโซลูชันที่สองของฉัน
amon

การปรับปรุงด้วยอัลกอริทึมที่ดียิ่งขึ้น
อมร

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

@MichaelT คุณถูกต้องที่เราสามารถสร้างตัวจำแนกลายมือด้วยวิธีนี้ อย่างไรก็ตามเราลดค่าใช้จ่ายในการเริ่มต้นเป็นn · O (B) + ในO (f (A))แล้ว อาคาร Trie เหมือนโครงสร้างของ Bs ทั้งหมดจะใช้เวลาบางอย่างเช่นO (n · B)ด้วยการจับคู่อยู่ในO (A) นี่เป็นโอกาสที่แท้จริงที่จะถูกกว่าในทางทฤษฎี (การสร้างกราฟในโซลูชันที่ 3 อาจมีราคาแพง แต่เป็นเพียงค่าใช้จ่ายครั้งเดียว) ฉันคิดว่ามีคู่ชีวิตที่ดีกว่าสำหรับA≫n · Bและมีข้อเสียที่ไม่สามารถจัดการอินพุตสตรีมมิ่ง - Bs ทั้งหมดต้องโหลดก่อนการจับคู่ ฉันอาจจะปรับปรุงคำตอบใน 6h
อมร

6

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

เพียงแค่ใส่ตัวเลขทั้งหมดของ A เป็นสตริงและตัวเลขทั้งหมดของ B (.*)เป็นสตริงแยกจากกันโดยการแสดงออกปกติ เพิ่ม^ตัวอักษรที่จุดเริ่มต้นและ$ตอนท้าย จากนั้นให้เครื่องมือค้นหานิพจน์ปกติที่คุณชื่นชอบค้นหารายการที่ตรงกันทั้งหมด ตัวอย่างเช่นเมื่อ

A = (1234356), B = (236)

สร้างประสบการณ์ reg สำหรับ B ^(.*)2(.*)3(.*)6(.*)$เช่น ตอนนี้ทำการค้นหา regexp ทั่วโลก หากต้องการทราบว่าตำแหน่งใดในการแข่งขัน A เรียงลำดับของคุณเพียงแค่ตรวจสอบความยาวของ 3 ส่งแรก

หากช่วงจำนวนเต็มของคุณเหลือ 0 ถึง 9 คุณอาจลองเข้ารหัสด้วยตัวอักษรเดี่ยวก่อนเพื่อให้สามารถใช้งานได้หรือคุณต้องปรับความคิดโดยใช้อักขระแยก

แน่นอนว่าความเร็วของวิธีการนั้นจะขึ้นอยู่กับความเร็วของเครื่องยนต์ reg exp ที่คุณใช้ แต่มีเอ็นจิ้นที่ได้รับการปรับปรุงให้ดีที่สุดและฉันคิดว่ามันยากที่จะใช้อัลกอริทึมที่เร็วกว่า "ออกจากกล่อง" .


หนึ่งไม่จำเป็นต้องไปตลอดทางเพื่อเรียกใช้ regex และเครื่องยนต์ มันจะเป็นไปได้ที่จะใช้ออปติคัลไฟไนต์ จำกัด แบบง่าย ๆ เพื่อรันมัน มันเป็นเส้นทาง 'เส้นตรง' ผ่าน

@MichaelT: อืมฉันไม่มีไลบรารี่ "finite automata ทั่วไป" และ OP ไม่ได้บอกเราเกี่ยวกับภาษาการเขียนโปรแกรมที่เขาใช้ " ซึ่งควรทำให้ข้อเสนอแนะของฉันง่ายต่อการใช้งานโดยมีโค้ดน้อยกว่ามากเช่นโซลูชันของ amon IMHO ผู้ปฏิบัติการควรจะลองดูถ้ามันช้าเกินไปสำหรับเขาเขาก็ยังสามารถลองได้หากวิธีแก้ปัญหาที่ซับซ้อนกว่านี้จะให้บริการเขาได้ดีขึ้น
Doc Brown

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

@MichaelT: ทำไมคุณไม่โพสต์ร่างของอัลกอริทึมนั้นเป็นคำตอบ?
Doc Brown

ส่วนใหญ่เป็นเพราะคำตอบที่ดีกว่าอยู่แล้ว - "เรารักษาเคอร์เซอร์สำหรับลำดับ A จากนั้นเราวนซ้ำรายการทั้งหมดในลำดับ B สำหรับแต่ละรายการเราเลื่อนเคอร์เซอร์ไปข้างหน้าใน A จนกว่าเราจะพบรายการที่ตรงกันหากไม่มี พบรายการที่ตรงกันจากนั้น B ไม่ใช่การเรียงลำดับ "

0

อัลกอริทึมนี้ควรจะค่อนข้างมีประสิทธิภาพถ้ารับความยาวและวนซ้ำลำดับนั้นมีประสิทธิภาพ

  1. เปรียบเทียบความยาวของทั้งสองลำดับ เก็บไว้นานขึ้นsequenceและสั้นลงsubsequence
  2. sequenceเริ่มต้นที่จุดเริ่มต้นของทั้งลำดับและห่วงจนกว่าจะสิ้นสุดของ
    1. คือตัวเลขที่ตำแหน่งปัจจุบันsequenceเท่ากับจำนวนที่ตำแหน่งปัจจุบันของsubsequence
    2. ถ้าใช่ย้ายทั้งสองตำแหน่งไปอีกหนึ่งครั้ง
    3. ถ้าไม่ย้ายตำแหน่งของsequenceอีกหนึ่งเท่านั้น
  3. เป็นตำแหน่งของsubsequenceเมื่อสิ้นสุดการsequence
  4. ถ้าใช่ทั้งสองจะจับคู่กัน
  5. ถ้าไม่ทั้งสองลำดับไม่ตรงกัน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.