Bir dizinin ortasını hesaplarken neden start + (bitiş - başlangıç) / 2 (başlangıç ​​+ bitiş) / 2'yi tercih etmelisiniz?


160

Programcıların formülü kullandığını gördüm

mid = start + (end - start) / 2

daha basit bir formül kullanmak yerine

mid = (start + end) / 2

dizideki veya listedeki orta öğeyi bulmak için.

Neden eskisini kullanıyorlar?


51
Vahşi tahmin: (start + end)taşabilir, ancak (end - start)yapamaz.
cadaniluk

30
çünkü ikincisi ne zaman startve endişaretçi çalışmaz .
ensc


20
start + (end - start) / 2Ayrıca semantik anlam taşır: (end - start)uzunluk, bu nedenle bu diyor: start + half the length.
njzk2

2
@ LưuVĩnhPhúc: Bu sorunun en iyi yanıtı ve en fazla oyu yok mu? Eğer öyleyse, diğer sorular muhtemelen bunun bir kopyası olarak kapatılmalıdır. Görevlerin yaşı ilgisizdir.
Nisse Engström

Yanıtlar:


218

Üç sebep var.

Her şeyden önce, 1 taşmadığı start + (end - start) / 2sürece işaretçiler kullanıyor olsanız bile çalışır .end - start

int *start = ..., *end = ...;
int *mid = start + (end - start) / 2; // works as expected
int *mid = (start + end) / 2;         // type error, won't compile

İkincisi, start + (end - start) / 2değil taşması durumunda olacak startve endbüyük pozitif sayılardır. İmzalı işlenenlerde taşma tanımsız:

int start = 0x7ffffffe, end = 0x7fffffff;
int mid = start + (end - start) / 2; // works as expected
int mid = (start + end) / 2;         // overflow... undefined

(Taşınabileceğini unutmayın end - start, ancak yalnızca start < 0veya end < 0.

Veya imzasız aritmetik ile taşma tanımlanır, ancak yanlış cevap verir. Ancak, imzasız işlenenler için start + (end - start) / 2asla taşmadığı sürece end >= start.

unsigned start = 0xfffffffeu, end = 0xffffffffu;
unsigned mid = start + (end - start) / 2; // works as expected
unsigned mid = (start + end) / 2;         // mid = 0x7ffffffe

Son olarak, genellikle startöğeye doğru yuvarlanmak istersiniz .

int start = -3, end = 0;
int mid = start + (end - start) / 2; // -2, closer to start
int mid = (start + end) / 2;         // -1, surprise!

Dipnotlar

1 C standardına göre, eğer işaretçi çıkarma sonucu a olarak gösterilemiyorsa ptrdiff_t, davranış tanımsızdır. Bununla birlikte, pratikte, bu char, tüm adres alanının en az yarısını kullanarak bir dizi tahsis edilmesini gerektirir .


arasında meydana (end - start)yılında signed into taştığında durum tanımlanmamış.
ensc

Taşmadığını kanıtlayabilir misin end-start? Bir negatif alırsanız AFAIK starttaşması mümkün olmalıdır. Tabii, çoğu zaman ortalama hesaplamak zaman değerleri olduğunu biliyorum >= 0...
Bakuriu

12
@Bakuriu: Doğru olmayan bir şeyi kanıtlamak imkansız.
Dietrich Epp

4
İşaretçi çıkarma (standart başına) tasarım gereği bozulduğu için C ile özellikle ilgilidir. end - startNesne boyutları imzasızken işaretçi farklılıkları imzalandığı için uygulamaların tanımsız olan çok büyük diziler oluşturmasına izin verilir . Dolayısıyla end - start, bir şekilde aşağıdaki dizinin boyutunu da tutmanız koşuluyla, "işaretçilerle bile çalışır" PTRDIFF_MAX. Standarda adil olmak gerekirse, çoğu mimaride bu bir engel değil, çünkü hafıza haritasının yarısı kadar.
Steve Jessop

3
@Bakuriu: Bu arada, bir şeyleri kaçırdığımı veya net olmayan bir şey olduğunu düşünüyorsanız, değişiklikleri önermek (veya bunları kendiniz yapmak) için kullanabileceğiniz bir "düzenle" düğmesi var. Ben sadece insanım ve bu yazı iki binden fazla göz küresi tarafından görüldü. "Açıklığa kavuşturmalısın ..." gibi bir yorum bana gerçekten yanlış bir şekilde ovalar.
Dietrich Epp

18

Bu gerçeği göstermek için basit bir örnek alabiliriz. Belirli bir büyük dizide, aralığın orta noktasını bulmaya çalıştığımızı varsayalım [1000, INT_MAX]. Şimdi, veri türünün depolayabileceği INT_MAXen büyük değerdir int. Buna 1eklenmiş olsa bile , nihai değer negatif olacaktır.

Ayrıca, start = 1000ve end = INT_MAX.

Formül kullanılarak (start + end)/2,

orta nokta

(1000 + INT_MAX)/2= -(INT_MAX+999)/2, negatiftir ve bu değeri kullanarak endekslemeye çalışırsak segmentasyon hatası verebilir .

Ancak, formülü kullanarak (start + (end-start)/2), şunu elde ederiz:

(1000 + (INT_MAX-1000)/2)= (1000 + INT_MAX/2 - 500)= (INT_MAX/2 + 500) taşmayacak .


1
İçine 1 eklerseniz INT_MAX, sonuç negatif olmaz, ancak tanımsız olur.
celtschk

@celtschk Teorik olarak, evet. Pratik olarak bunun etrafını dolaşan olacak giden kez bir çok INT_MAXiçin -INT_MAX. Yine de buna güvenmek kötü bir alışkanlık.
Mast

17

Başkalarının söylediklerine eklemek için, ilki anlamını daha az matematik düşünenlere daha açık bir şekilde açıklar:

mid = start + (end - start) / 2

şöyle okur:

orta başlangıç ​​artı uzunluğunun yarısına eşittir.

buna karşılık:

mid = (start + end) / 2

şöyle okur:

orta, başlangıç ​​ve sonun yarısına eşittir

En azından böyle ifade edildiğinde, birincisi kadar net görünmüyor.

Kos'un işaret ettiği gibi şunları da okuyabilir:

orta, başlangıç ​​ve bitiş ortalamasına eşittir

En azından benim görüşüme göre, birincisi kadar net ama yine de değil.


3
Ne demek istediğini anlıyorum, ama bu gerçekten bir esneme. "E - s" yi görürseniz ve "uzunluk" düşünürseniz, neredeyse "" s + e) ​​/ 2 "yi görürsünüz ve" ortalama "veya" orta "dır.
djechlin

2
@djechlin Programcılar matematikte fakirdir. İşlerini yapmakla meşguller. Matematik derslerine katılmak için zamanları yok.
Küçük Uzaylı

1

start + (end-start) / 2 olası taşmayı önleyebilir, örneğin start = 2 ^ 20 ve end = 2 ^ 30

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.