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


143

ฉันมีอาร์เรย์ JavaScript ที่เรียงลำดับแล้วและต้องการแทรกอีกหนึ่งรายการลงในอาร์เรย์ดังนั้นอาร์เรย์ที่ได้จะยังคงเรียงลำดับอยู่ แน่นอนฉันสามารถใช้ฟังก์ชั่นการแทรกสไตล์ quicksort ง่าย ๆ :

var array = [1,2,3,4,5,6,7,8,9];
var element = 3.5;
function insert(element, array) {
  array.splice(locationOf(element, array) + 1, 0, element);
  return array;
}

function locationOf(element, array, start, end) {
  start = start || 0;
  end = end || array.length;
  var pivot = parseInt(start + (end - start) / 2, 10);
  if (end-start <= 1 || array[pivot] === element) return pivot;
  if (array[pivot] < element) {
    return locationOf(element, array, pivot, end);
  } else {
    return locationOf(element, array, start, pivot);
  }
}

console.log(insert(element, array));

[คำเตือน] รหัสนี้มีข้อผิดพลาดเมื่อพยายามที่จะแทรกไปยังจุดเริ่มต้นของอาร์เรย์เช่นinsert(2, [3, 7 ,9]) ผลิตไม่ถูกต้อง [3, 2, 7, 9]

อย่างไรก็ตามฉันสังเกตเห็นว่าการใช้งานของฟังก์ชั่น Array.sort อาจทำสิ่งนี้สำหรับฉันและโดยพื้นฐาน:

var array = [1,2,3,4,5,6,7,8,9];
var element = 3.5;
function insert(element, array) {
  array.push(element);
  array.sort(function(a, b) {
    return a - b;
  });
  return array;
}

console.log(insert(element, array));

มีเหตุผลที่ดีในการเลือกการใช้งานครั้งแรกในช่วงที่สองหรือไม่?

แก้ไข : โปรดทราบว่าสำหรับกรณีทั่วไปการแทรก O (บันทึก (n)) (ตามที่นำมาใช้ในตัวอย่างแรก) จะเร็วกว่าอัลกอริทึมการเรียงลำดับทั่วไป อย่างไรก็ตามนี่ไม่จำเป็นว่าในกรณีของ JavaScript โดยเฉพาะ โปรดทราบว่า:

  • กรณีที่ดีที่สุดสำหรับอัลกอริธึมการแทรกหลายตัวคือ O (n) ซึ่งยังคงแตกต่างอย่างมีนัยสำคัญจาก O (log (n)) แต่ก็ไม่เลวเท่า O (n log (n)) ดังที่กล่าวไว้ด้านล่าง มันจะลงมาที่อัลกอริทึมการเรียงลำดับเฉพาะที่ใช้ (ดูการใช้Javascript Array.sort หรือไม่ )
  • วิธีการเรียงลำดับใน JavaScript เป็นฟังก์ชั่นพื้นฐานดังนั้นการตระหนักถึงประโยชน์ที่ยิ่งใหญ่ - O (log (n)) ที่มีค่าสัมประสิทธิ์มหาศาลยังคงแย่กว่า O (n) สำหรับชุดข้อมูลที่มีขนาดพอสมควร

การใช้รอยต่อในการนำไปใช้ครั้งที่สองนั้นสิ้นเปลืองไปเล็กน้อย ทำไมไม่ใช้การกด
Breton

จุดดีฉันเพิ่งคัดลอกมาจากครั้งแรก
Elliot Kroo

4
สิ่งใดก็ตามที่มีsplice()(เช่นตัวอย่างที่ 1 ของคุณ) เป็น O (n) แล้ว แม้ว่ามันจะไม่ได้สร้างสำเนาทั้งหมดของอาเรย์ใหม่ทั้งหมดภายในมันก็อาจจะต้องแบ่งไอเท็ม n ทั้งหมดกลับเป็น 1 ตำแหน่งถ้าองค์ประกอบจะถูกแทรกในตำแหน่งที่ 0 บางทีมันอาจจะเร็วเพราะมันเป็นฟังก์ชั่นพื้นฐานและค่าคงที่ ต่ำ แต่ O (n) ยังคงอยู่
j_random_hacker

6
นอกจากนี้สำหรับการอ้างอิงในอนาคตสำหรับผู้ที่ใช้รหัสนี้รหัสมีข้อผิดพลาดเมื่อพยายามแทรกไปยังจุดเริ่มต้นของอาร์เรย์ ดูเพิ่มเติมสำหรับรหัสที่แก้ไข
Pinocchio

3
อย่าparseIntใช้Math.floorแทน Math.floorเร็วกว่ามากparseInt: jsperf.com/test-parseint-and-math-floor
Hubert Schölnast

คำตอบ:


58

เช่นเดียวกับที่เป็นจุดข้อมูลเดียวสำหรับการเตะฉันทดสอบการแทรกองค์ประกอบสุ่ม 1,000 รายการลงในอาร์เรย์ของตัวเลขที่จัดเรียงไว้ล่วงหน้า 100,000 รายการโดยใช้สองวิธีโดยใช้ Chrome บน Windows 7:

First Method:
~54 milliseconds
Second Method:
~57 seconds

อย่างน้อยในการตั้งค่านี้วิธีดั้งเดิมจะไม่ชดเชย สิ่งนี้เป็นจริงแม้สำหรับชุดข้อมูลขนาดเล็กการแทรก 100 องค์ประกอบลงในอาร์เรย์ 1,000:

First Method:
1 milliseconds
Second Method:
34 milliseconds

1
arrays.sort ฟังดูแย่มาก
njzk2

2
ดูเหมือนว่า array.splice จะต้องทำสิ่งที่ฉลาดจริงๆเพื่อแทรกองค์ประกอบเดียวภายใน 54 microseconds
gnasher729

@ gnasher729 - ฉันไม่คิดว่าอาร์เรย์ของ Javascript นั้นเหมือนกับอาร์เรย์ต่อเนื่องทางกายภาพอย่างที่เรามีในซีฉันคิดว่าเอ็นจิ้น JS สามารถใช้มันเป็นแผนที่ / พจนานุกรมที่เปิดใช้งานการแทรกด่วน
เอียน

1
เมื่อคุณใช้ฟังก์ชั่นการเปรียบเทียบกับArray.prototype.sortคุณสูญเสียผลประโยชน์ของ C ++ เพราะฟังก์ชั่น JS นั้นเรียกว่ามาก
aleclarson

ไม่วิธีแรกเปรียบเทียบว่าในขณะนี้ว่าChrome ใช้ TimSort ? จากTimSort Wikipedia : "ในกรณีที่ดีที่สุดซึ่งเกิดขึ้นเมื่ออินพุตถูกเรียงลำดับแล้ว [TimSort] จะทำงานในเวลาเชิงเส้น"
poshest

47

ง่าย ๆ ( สาธิต ):

function sortedIndex(array, value) {
    var low = 0,
        high = array.length;

    while (low < high) {
        var mid = (low + high) >>> 1;
        if (array[mid] < value) low = mid + 1;
        else high = mid;
    }
    return low;
}

4
สัมผัสที่ดี ฉันไม่เคยได้ยินการใช้ตัวดำเนินการ bitwise เพื่อหาค่ากลางของตัวเลขสองตัว ปกติฉันจะคูณด้วย 0.5 มีการเพิ่มประสิทธิภาพที่สำคัญอย่างนี้ไหม
แจ็กสัน

2
@ แจ็คสันx >>> 1เปลี่ยนตำแหน่งแบบไบนารี่ขวา 1 ตำแหน่งซึ่งเป็นเพียงการหาร 2 อย่างมีประสิทธิภาพเช่น 11: 1011-> 101ผลลัพธ์ถึง 5
Qwerty

3
@Qwerty @Web_Designer เมื่อติดตามแล้วคุณสามารถอธิบายความแตกต่างระหว่าง>>> 1และ ( เห็น ที่นี่ กับที่ นั่น ) >> 1หรือไม่
yckart

4
>>>เป็นการเปลี่ยนแปลงที่ถูกต้องที่ไม่ได้ลงชื่อในขณะที่>>เป็นการขยายสัญญาณ - ทั้งหมดนี้จะลดลงไปสู่การเป็นตัวแทนในหน่วยความจำของตัวเลขติดลบโดยที่ค่าบิตสูงจะถูกตั้งค่าหากเป็นลบ ดังนั้นถ้าคุณเปลี่ยน0b1000ที่เหมาะสมกับสถานที่ 1 >>คุณจะได้รับ0b1100ถ้าคุณใช้แทนคุณจะได้รับ>>> 0b0100ในขณะที่ในกรณีที่ได้รับคำตอบมันไม่สำคัญเลย (ตัวเลขที่ถูกเลื่อนโดยไม่ใหญ่กว่าค่าสูงสุดของจำนวนเต็มบวก 32 บิตบวกหรือลบ) ที่มีการเซ็นชื่อเป็นสิ่งสำคัญที่จะต้องใช้สิ่งที่ถูกต้องในทั้งสองกรณี (คุณ ต้องเลือกกรณีที่คุณต้องจัดการ)
asherkin

2
@asherkin - สิ่งนี้ไม่ถูกต้อง: "ถ้าคุณย้ายที่0b10001 ที่ที่ถูกต้อง>>คุณจะได้0b1100" 0b0100ไม่มีคุณจะได้รับ ผลลัพธ์ของโอเปอเรเตอร์กะขวาที่แตกต่างกันจะเหมือนกันสำหรับค่าทั้งหมดยกเว้นจำนวนลบและตัวเลขที่มากกว่า 2 ^ 31 (เช่นตัวเลขที่มี 1 ในบิตแรก)
gilly3

29

คำถามที่ดีและน่าทึ่งมากพร้อมการสนทนาที่น่าสนใจมาก! ฉันยังใช้Array.sort()ฟังก์ชั่นนี้หลังจากกดองค์ประกอบเดียวในอาร์เรย์ที่มีวัตถุหลายพันรายการ

ฉันต้องขยายlocationOfฟังก์ชั่นของคุณเพื่อวัตถุประสงค์ของฉันเพราะมีวัตถุที่ซับซ้อนและดังนั้นจึงจำเป็นต้องมีฟังก์ชั่นการเปรียบเทียบเช่นในArray.sort():

function locationOf(element, array, comparer, start, end) {
    if (array.length === 0)
        return -1;

    start = start || 0;
    end = end || array.length;
    var pivot = (start + end) >> 1;  // should be faster than dividing by 2

    var c = comparer(element, array[pivot]);
    if (end - start <= 1) return c == -1 ? pivot - 1 : pivot;

    switch (c) {
        case -1: return locationOf(element, array, comparer, start, pivot);
        case 0: return pivot;
        case 1: return locationOf(element, array, comparer, pivot, end);
    };
};

// sample for objects like {lastName: 'Miller', ...}
var patientCompare = function (a, b) {
    if (a.lastName < b.lastName) return -1;
    if (a.lastName > b.lastName) return 1;
    return 0;
};

7
ดูเหมือนว่าน่าสังเกตว่าสำหรับรุ่นนี้จะทำงานได้อย่างถูกต้องเมื่อพยายามแทรกไปยังจุดเริ่มต้นของอาร์เรย์ (มันมูลค่าการกล่าวขวัญเพราะรุ่นในคำถามเดิมมีข้อผิดพลาดและไม่ทำงานอย่างถูกต้องสำหรับกรณีที่.)
garyrob

3
ฉันไม่แน่ใจว่าการใช้งานของฉันแตกต่างกันหรือไม่ แต่ฉันต้องเปลี่ยนไตรภาคเป็นreturn c == -1 ? pivot : pivot + 1;เพื่อส่งคืนดัชนีที่ถูกต้อง มิฉะนั้นสำหรับอาร์เรย์ที่มีความยาว 1 ฟังก์ชันจะคืนค่า -1 หรือ 0
Niel

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

1
@TheRedPea: ไม่ฉันหมายถึง>> 1ควรจะเร็วกว่า (หรือไม่ช้ากว่า) กว่า/ 2
kwrl

1
ฉันสามารถดูปัญหาที่อาจเกิดขึ้นกับผลลัพธ์ของcomparerฟังก์ชัน ในขั้นตอนวิธีนี้มันจะถูกเมื่อเทียบกับ+-1แต่มันอาจจะคุ้มค่าโดยพล/<0 >0ดูฟังก์ชั่นเปรียบเทียบ ส่วนที่มีปัญหาไม่เพียง แต่เป็นswitchคำสั่งเท่านั้น แต่ยังรวมถึงบรรทัดด้วย: if (end - start <= 1) return c == -1 ? pivot - 1 : pivot;ซึ่งcจะถูกเปรียบเทียบ-1ด้วยเช่นกัน
eXavier

19

มีข้อบกพร่องในรหัสของคุณ คุณควรอ่าน:

function locationOf(element, array, start, end) {
  start = start || 0;
  end = end || array.length;
  var pivot = parseInt(start + (end - start) / 2, 10);
  if (array[pivot] === element) return pivot;
  if (end - start <= 1)
    return array[pivot] > element ? pivot - 1 : pivot;
  if (array[pivot] < element) {
    return locationOf(element, array, pivot, end);
  } else {
    return locationOf(element, array, start, pivot);
  }
}

หากไม่มีการแก้ไขนี้รหัสจะไม่สามารถแทรกองค์ประกอบที่จุดเริ่มต้นของอาร์เรย์ได้


ทำไมคุณถึง int 0 กับ? เช่นสิ่งที่เริ่ม | | 0 ทำ
Pinocchio

3
@Pinocchio: เริ่มต้น || 0 สั้นเทียบเท่า: ถ้า (! start) start = 0; - อย่างไรก็ตามเวอร์ชัน "ยาวกว่า" มีประสิทธิภาพมากกว่าเนื่องจากไม่ได้กำหนดตัวแปรให้กับตัวเอง
SuperNova

11

ฉันรู้ว่านี่เป็นคำถามเก่าที่มีคำตอบแล้วและมีคำตอบที่ดีอีกจำนวนมาก ฉันเห็นคำตอบบางอย่างที่เสนอว่าคุณสามารถแก้ปัญหานี้ได้โดยค้นหาดัชนีการแทรกที่ถูกต้องใน O (log n) - คุณทำได้ แต่คุณไม่สามารถแทรกในเวลานั้นได้เนื่องจากอาร์เรย์ต้องถูกคัดลอกบางส่วนเพื่อให้ ช่องว่าง

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

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

function addAndSort(arr, val) {
    arr.push(val);
    for (i = arr.length - 1; i > 0 && arr[i] < arr[i-1]; i--) {
        var tmp = arr[i];
        arr[i] = arr[i-1];
        arr[i-1] = tmp;
    }
    return arr;
}

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

ปรับปรุง:

สิ่งนี้อาจเร็วกว่า:

function addAndSort2(arr, val) {
    arr.push(val);
    i = arr.length - 1;
    item = arr[i];
    while (i > 0 && item < arr[i-1]) {
        arr[i] = arr[i-1];
        i -= 1;
    }
    arr[i] = item;
    return arr;
}

อัปเดตลิงก์ JS Bin


ใน JavaScript การเรียงลำดับการแทรกที่คุณเสนอจะช้ากว่าวิธีการค้นหาแบบทวิภาคและการต่อรอยเนื่องจากตัวต่อมีการใช้งานที่รวดเร็ว
trincot

เว้นแต่ว่าจาวาสคริปต์อย่างใดสามารถทำลายกฎหมายของความซับซ้อนของเวลาฉันไม่เชื่อ คุณมีตัวอย่างที่สามารถรันได้ของวิธีการค้นหาแบบไบนารีและวิธีต่อกันเร็วกว่าหรือไม่?
domoarigato

ฉันนำความคิดเห็นที่สองของฉันกลับมา ;-) แน่นอนว่าจะมีขนาดอาร์เรย์ที่เกินกว่าที่โซลูชัน B-tree จะมีประสิทธิภาพสูงกว่าโซลูชันต่อกัน
trincot

9

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

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

ขั้นตอนวิธีการจัดเรียงทั่วไปมักจะเป็นค่าเฉลี่ยO (n ⋅ล็อก (n))และขึ้นอยู่กับการดำเนินการที่เป็นจริงอาจจะมีกรณีที่เลวร้ายที่สุดถ้าอาร์เรย์จะเรียงลำดับแล้วนำไปสู่ความซับซ้อนของO (n 2 ) ค้นหาตำแหน่งการแทรกโดยตรงแทนมีความซับซ้อนของO (log (n))ดังนั้นมันจะเร็วขึ้นเสมอ


เป็นที่น่าสังเกตว่าการแทรกองค์ประกอบลงในอาร์เรย์นั้นมีความซับซ้อนของ O (n) ดังนั้นผลลัพธ์ที่ได้ควรจะเหมือนกัน
NemPlayer

5

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

ฉันลงเอยด้วยการเขียนฟังก์ชั่นการค้นหา / ใส่ไบนารีที่ลื่นไหลเพื่อจุดประสงค์นี้ฉันจึงคิดว่าฉันจะแบ่งปันมัน เนื่องจากมันใช้การwhileวนซ้ำแทนการเรียกซ้ำไม่มีการได้ยินการเรียกฟังก์ชั่นพิเศษดังนั้นฉันจึงคิดว่าประสิทธิภาพจะดีขึ้นกว่าวิธีการโพสต์แบบเดิม และจะเลียนแบบตัวArray.sort()เปรียบเทียบเริ่มต้นตามค่าเริ่มต้น แต่ยอมรับฟังก์ชันตัวเปรียบเทียบแบบกำหนดเองหากต้องการ

function insertSorted(arr, item, comparator) {
    if (comparator == null) {
        // emulate the default Array.sort() comparator
        comparator = function(a, b) {
            if (typeof a !== 'string') a = String(a);
            if (typeof b !== 'string') b = String(b);
            return (a > b ? 1 : (a < b ? -1 : 0));
        };
    }

    // get the index we need to insert the item at
    var min = 0;
    var max = arr.length;
    var index = Math.floor((min + max) / 2);
    while (max > min) {
        if (comparator(item, arr[index]) < 0) {
            max = index;
        } else {
            min = index + 1;
        }
        index = Math.floor((min + max) / 2);
    }

    // insert the item
    arr.splice(index, 0, item);
};

หากคุณเปิดให้ใช้ห้องสมุดอื่น ๆ lodash ให้sortedIndexและsortedLastIndexฟังก์ชั่นซึ่งสามารถนำมาใช้ในสถานที่ของwhileวง ข้อเสียที่เป็นไปได้สองข้อคือ 1) ประสิทธิภาพไม่ดีเท่าวิธีการของฉัน (คิดว่าฉันไม่แน่ใจว่าแย่กว่านั้นมาก) และ 2) ไม่ยอมรับฟังก์ชันตัวเปรียบเทียบแบบกำหนดเองเพียงวิธีการเพื่อรับค่าเพื่อเปรียบเทียบ (โดยใช้เครื่องมือเปรียบเทียบเริ่มต้นฉันถือว่า)


การเรียกร้องให้arr.splice()มีความซับซ้อนของเวลา O (n) อย่างแน่นอน
domoarigato

4

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

อย่างไรก็ตามยังแก้ปัญหาได้จริง

เมื่อฉันอ่านว่าคุณต้องการเรียงลำดับความคิดแรกของฉันคือใช้การเรียงลำดับแทรก! . มันมีประโยชน์เพราะมันทำงานในเวลาเชิงเส้นในรายการเรียงลำดับหรือเกือบเรียงลำดับมันวิ่งในเส้นเวลาในการเรียงหรือเกือบเรียงลำดับรายการเนื่องจากอาร์เรย์ของคุณมีองค์ประกอบเพียง 1 รายการเท่านั้นซึ่งนับเป็นเกือบเรียงลำดับแล้ว (ยกเว้น, ดี, อาร์เรย์ที่มีขนาด 2 หรือ 3 หรืออะไรก็ตาม แต่ ณ จุดนั้น c'mon) ตอนนี้การใช้การเรียงลำดับนั้นไม่ได้แย่เกินไป แต่มันก็เป็นเรื่องยุ่งยากที่คุณอาจไม่ต้องการจัดการและอีกครั้งฉันไม่รู้อะไรเกี่ยวกับจาวาสคริปต์และถ้ามันง่ายหรือยากหรืออะไรก็ตาม สิ่งนี้จะขจัดความต้องการฟังก์ชั่นการค้นหาของคุณและคุณเพียงแค่กด (ตามที่ Breton แนะนำ)

ประการที่สองฟังก์ชั่นการค้นหา "quicksort-esque" ของคุณน่าจะเป็นอัลกอริธึมการค้นหาแบบไบนารี่ ! มันเป็นอัลกอริธึมที่ดีมากใช้งานง่ายและรวดเร็ว แต่ด้วยการตรวจจับเพียงครั้งเดียวมันยากที่จะนำไปใช้อย่างถูกต้อง ฉันจะไม่กล้าพูดว่าของคุณถูกต้องหรือไม่ (ฉันหวังว่ามันจะเป็นแน่นอน! :)) แต่ต้องระวังถ้าคุณต้องการใช้มัน

อย่างไรก็ตามสรุป: การใช้ "push" กับการเรียงลำดับการแทรกจะทำงานในเวลาเชิงเส้น (สมมติว่าส่วนที่เหลือของอาร์เรย์จะถูกจัดเรียง) และหลีกเลี่ยงข้อกำหนดขั้นตอนวิธีการค้นหาแบบไบนารีที่ยุ่งเหยิง ฉันไม่รู้ว่านี่เป็นวิธีที่ดีที่สุดหรือไม่ (การใช้งานอาร์เรย์อาจเป็นฟังก์ชั่นในตัวที่ดีกว่าใครจะรู้) แต่ดูเหมือนว่าสมเหตุสมผลสำหรับฉัน :) - Agor


1
+1 เนื่องจากสิ่งที่มีsplice()อยู่แล้ว O (n) แม้ว่ามันจะไม่ได้สร้างสำเนาทั้งหมดของอาเรย์ใหม่ทั้งหมดภายในมันก็อาจจะต้องแบ่งรายการทั้งหมด n รายการกลับไป 1 ตำแหน่งถ้าองค์ประกอบจะถูกแทรกในตำแหน่ง 0
j_random_hacker

ฉันเชื่อว่าการเรียงลำดับการแทรกยังเป็นกรณีที่ดีที่สุด O (n) และ O (n ^ 2) กรณีที่แย่ที่สุด (แม้ว่ากรณีการใช้งานของ OP น่าจะเป็นกรณีที่ดีที่สุด)
domoarigato

ลบหนึ่งสำหรับพูดลงไปที่ OP วรรคแรกรู้สึกเหมือนเป็นการตักเตือนที่ไม่จำเป็นว่าไม่รู้ว่าประกบกันอย่างไรภายใต้ประทุน
Matt Zera

2

ต่อไปนี้เป็นการเปรียบเทียบอัลกอริทึมที่แตกต่างกันสี่แบบเพื่อให้บรรลุสิ่งนี้: https://jsperf.com/sorted-array-insert-comparison/1

อัลกอริทึม

  • ไร้เดียงสา: เพียงแค่กดและจัดเรียง () หลังจากนั้น
  • เชิงเส้น: วนซ้ำแถวลำดับและแทรกตามความเหมาะสม
  • การค้นหาแบบไบนารี: นำมาจากhttps://stackoverflow.com/a/20352387/154329
  • "Quick Sort Like": โซลูชันการกลั่นจาก Syntheticzero ( https://stackoverflow.com/a/18341744/154329 )

ไร้เดียงสาน่ากลัวอยู่เสมอ ดูเหมือนว่าสำหรับอาเรย์ขนาดเล็กส่วนอีกสามตัวนั้นไม่ได้แตกต่างกันมากนัก แต่สำหรับอาเรย์ที่มีขนาดใหญ่กว่า 2 ตัวสุดท้ายนั้นดีกว่าวิธีการเชิงเส้นอย่างง่าย


ทำไมไม่ทดสอบโครงสร้างข้อมูลที่ออกแบบมาเพื่อใช้การแทรกและการค้นหาที่รวดเร็ว อดีต ข้ามรายการและ BST stackoverflow.com/a/59870937/3163618
qwr

Native เปรียบเทียบว่าChrome ใช้ TimSortอย่างไร จากTimSort Wikipedia : "ในกรณีที่ดีที่สุดซึ่งเกิดขึ้นเมื่ออินพุตถูกเรียงลำดับแล้วมันจะทำงานในเวลาเชิงเส้น"
poshest

2

นี่คือรุ่นที่ใช้ lodash

const _ = require('lodash');
sortedArr.splice(_.sortedIndex(sortedArr,valueToInsert) ,0,valueToInsert);

หมายเหตุ: SortIndex ทำการค้นหาแบบไบนารี


1

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

ต้นไม้เพื่อสถิติที่ช่วยให้การจัดทำดัชนีเวลาเข้าสู่ระบบด้วยฟังก์ชั่นการจัดอันดับ

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

ไม่มีคำตอบใด ๆ ที่การใช้งานarray.splice()มีประสิทธิภาพเลยเนื่องจากเป็นเวลา O (n) โดยเฉลี่ย ความซับซ้อนของเวลาใน array.splice () ใน Google Chrome คืออะไร


คำตอบนี้เป็นอย่างไรIs there a good reason to choose [splice into location found] over [push & sort]?
greybeard

1
@greybeard มันเป็นคำตอบของชื่อ ทางเลือกที่ไม่มีประสิทธิภาพ
qwr

ตัวเลือกจะไม่มีประสิทธิภาพหากพวกเขาเกี่ยวข้องกับการคัดลอกองค์ประกอบหลายอย่างของอาร์เรย์ไป
qwr

1

นี่คือฟังก์ชั่นของฉันใช้การค้นหาแบบไบนารีเพื่อค้นหารายการจากนั้นแทรกอย่างเหมาะสม:

function binaryInsert(val, arr){
    let mid, 
    len=arr.length,
    start=0,
    end=len-1;
    while(start <= end){
        mid = Math.floor((end + start)/2);
        if(val <= arr[mid]){
            if(val >= arr[mid-1]){
                arr.splice(mid,0,val);
                break;
            }
            end = mid-1;
        }else{
            if(val <= arr[mid+1]){
                arr.splice(mid+1,0,val);
                break;
            }
            start = mid+1;
        }
    }
    return arr;
}

console.log(binaryInsert(16, [
    5,   6,  14,  19, 23, 44,
   35,  51,  86,  68, 63, 71,
   87, 117
 ]));


0

อย่าเรียงลำดับใหม่หลังจากทุกรายการ

หากมีเพียงหนึ่งรายการที่จะแทรกคุณสามารถค้นหาตำแหน่งที่จะแทรกโดยใช้การค้นหาแบบไบนารี จากนั้นใช้ memcpy หรือคล้ายกันเพื่อคัดลอกรายการที่เหลือจำนวนมากเพื่อเพิ่มพื้นที่ว่างสำหรับรายการที่แทรก การค้นหาแบบไบนารีคือ O (log n) และการคัดลอกคือ O (n) โดยให้ O (n + log n) ทั้งหมด เมื่อใช้วิธีการด้านบนคุณจะทำการเรียงลำดับใหม่หลังจากการแทรกทุกครั้งซึ่งก็คือ O (n log n)

มันสำคัญไหม สมมติว่าคุณกำลังแทรกองค์ประกอบ k แบบสุ่มโดยที่ k = 1,000 รายการที่เรียงลำดับคือ 5000 รายการ

  • Binary search + Move = k*(n + log n) = 1000*(5000 + 12) = 5,000,012 = ~5 million ops
  • Re-sort on each = k*(n log n) = ~60 million ops

หากรายการ k ที่จะแทรกมาถึงเมื่อใดก็ตามที่คุณจะต้องทำการค้นหา + ย้าย อย่างไรก็ตามหากคุณได้รับรายการ k ที่จะแทรกลงในอาร์เรย์ที่เรียงลำดับก่อนเวลาคุณสามารถทำได้ดียิ่งขึ้น จัดเรียงรายการ k แยกจากอาร์เรย์ที่เรียงลำดับแล้ว n จากนั้นทำการเรียงลำดับการสแกนที่คุณเลื่อนลงทั้งสองแถวเรียงพร้อมกันโดยรวมหนึ่งเข้ากับอื่น ๆ - การเรียงเวียนผสานแบบขั้นตอนเดียว = บันทึก k k + n = 9965 + 5000 = ~ 15,000 ops

อัปเดต: เกี่ยวกับคำถามของคุณ
First method = binary search+move = O(n + log n). Second method = re-sort = O(n log n)อธิบายการกำหนดเวลาที่คุณได้รับอย่างชัดเจน


ใช่ แต่ไม่ขึ้นอยู่กับอัลกอริทึมการเรียงลำดับของคุณ การใช้การเรียงลำดับฟองในลำดับย้อนกลับการเรียงลำดับของคุณหากองค์ประกอบสุดท้ายไม่ได้เรียงอยู่ใน o (n)
njzk2

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