質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

2回答

959閲覧

ソートアルゴリズムの改良について

majiponi

総合スコア1722

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2020/05/15 12:43

編集2020/05/16 01:26

最近、AtCoderを始めた初心者です。チュートリアルの問題を解いたのですが、もう少しスマートな書き方や、効率のよいアルゴリズムがあればと思い質問しました。質問内容は、

  1. もっと簡潔な書き方はないか?
  2. マージのアルゴリズムを改良できないか、in-placeにできないか? (log(Combination(2n, n))=O(n)?)
  3. n=5の場合のチューニングをコンパイル時に自動でさせる方法はないか? (保守性が高い、メタな表記がありそう)

の3点です。自力だと力業でゴリ押す汚いコードしか書けないものですから、コンテストに参加するにはまだまだ力不足と痛感しておりますが、ご教授教示いただけますと助かります。

問題文

C++

1#include <iostream> 2#include <algorithm> 3#include <type_traits> 4#include <utility> 5#include <vector> 6 7namespace practiceB{ 8 9template <class RandomAccessIterator, class Compare> 10void sort(const RandomAccessIterator first, const RandomAccessIterator last, Compare comp) 11{ 12 std::size_t n = std::distance(first, last); 13 if(n < 2) return; 14 if(n != 5){ 15 auto mid = first + n / 2; 16 practiceB::sort(first, mid, comp); 17 practiceB::sort(mid, last, comp); 18 19 std::vector<typename std::remove_const<typename std::remove_reference<decltype(*first)>::type>::type> tmp(n); 20 std::merge(first, mid, mid, last, tmp.begin(), comp); 21 std::move(tmp.begin(), tmp.end(), first); 22 } 23 else{ 24 if(!comp(first[0], first[1])){ 25 std::swap(first[0], first[1]); 26 } 27 if(!comp(first[2], first[3])){ 28 std::swap(first[2], first[3]); 29 } 30 if(!comp(first[0], first[2])){ 31 std::swap(first[0], first[2]); 32 std::swap(first[1], first[3]); 33 } 34 if(comp(first[2], first[4])){ 35 if(comp(first[3], first[4])){ 36 if(comp(first[1], first[3])){ 37 if(!comp(first[1], first[2])){ 38 std::swap(first[1], first[2]); 39 } 40 } 41 else{ 42 std::swap(first[1], first[2]); 43 std::swap(first[2], first[3]); 44 if(!comp(first[3], first[4])){ 45 std::swap(first[3], first[4]); 46 } 47 } 48 } 49 else{ 50 if(comp(first[1], first[4])){ 51 std::swap(first[3], first[4]); 52 if(!comp(first[1], first[2])){ 53 std::swap(first[1], first[2]); 54 } 55 } 56 else{ 57 std::swap(first[1], first[2]); 58 std::swap(first[2], first[4]); 59 if(!comp(first[3], first[4])){ 60 std::swap(first[3], first[4]); 61 } 62 } 63 } 64 } 65 else{ 66 if(comp(first[0], first[4])){ 67 if(comp(first[1], first[2])){ 68 std::swap(first[2], first[3]); 69 std::swap(first[2], first[4]); 70 if(!comp(first[1], first[2])){ 71 std::swap(first[1], first[2]); 72 } 73 } 74 else{ 75 std::swap(first[1], first[4]); 76 if(!comp(first[3], first[4])){ 77 std::swap(first[3], first[4]); 78 } 79 } 80 } 81 else{ 82 std::swap(first[0], first[4]); 83 std::swap(first[1], first[4]); 84 if(comp(first[2], first[4])){ 85 if(!comp(first[3], first[4])){ 86 std::swap(first[3], first[4]); 87 } 88 } 89 else{ 90 std::swap(first[2], first[4]); 91 std::swap(first[3], first[4]); 92 } 93 } 94 } 95 } 96} 97 98} 99

5/16編集:標準テンプレートにmergeが実装されているらしいので、それを使うようにしました。関数が減り、見やすくはなりましたが依然としてn=5の場合がカオスです…。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Orlofsky

2020/05/15 14:45

教授 → 教示
cateye

2020/05/16 01:18

マージソートは、安定ソートですが。それの拘らなくても良いのですか?
majiponi

2020/05/16 01:23

compを呼ぶ回数を最小に > 簡潔性、保守性 >>> 必要メモリの少なさ、安定ソート これぐらいの認識です。中身の要素はすべて異なる、という前提なので、安定ソートである必要は、ないです。
Zuishin

2020/05/16 01:41 編集

どうしてもマージソートと特定のケースへの条件分岐が必要なんですか? それをスマートと呼ぶのは非常に抵抗がありますが。 もしそうであるなら「スマート」を定義してください。
Orlofsky

2020/05/16 03:47

教授教示 → 教示
guest

回答2

0

ベストアンサー

  1. もっと簡潔な書き方はないか?

マージソートをやめてクイックソートにするのが良いと思います。
最初の比較で重い方をピボットに選べば N == 5 の時に 6 回以内の比較で終了します。このため、他のケースと場合分けする必要がありません。

  1. マージのアルゴリズムを改良できないか、in-placeにできないか? (log(Combination(2n, n))=O(n)?)

アルゴリズムの特性上、要素の挿入が必要となり、in-place にすると大量の移動が発生します。改良にはなりません。クイックソートならば、挿入が必要ありません。

ただし、配列の代わりに連結リストを用いることにより、挿入の負荷を減らすことはできます。しかしこの場合はノード操作自体の負荷がかかるので、高々 26 要素のソートであれば、かえって性能が落ちることが予想されます。

  1. n=5の場合のチューニングをコンパイル時に自動でさせる方法はないか? (保守性が高い、メタな表記がありそう)

ソートアルゴリズムをこのケースに最適化させるのが良いと思います。

投稿2020/05/15 23:56

編集2020/05/16 00:35
Zuishin

総合スコア28669

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2020/05/16 00:28 編集

最悪のケースとなるソート済みの入力 1, 2, 3, 4, 5 を考えます。 最初に 1 と 2 を取り出して比較し、2 をピボットに選びます。 比較回数を極力減らすため、アルゴリズムは、前半をソートし、ピボットをその後に置き、後半をソートすることで全体のソートが成るということにします。 次に 2 と残りの要素を比較し、小さいものを前半に、大きいものを後半に集めます。 前半は 1 になったので、要素数 1 で確定です。次にピボットの 2 が置かれます。 残り 3, 4, 5 をクイックソートします。 まず 3 と 4 を取り出して 4 をピボットに選びます。 4 と 5 を比較することで前半は 3 に、後半は 5 に確定しました。 比較は 1-2, 2-3, 2-4, 2-5, 3-4, 4-5 の 6 回で、これが最悪のケースです。なお、最良のケースは 3 を最初のピボットに選ぶことによる 6 回の比較なので、この場合は最悪と最良の差はありません。
Zuishin

2020/05/16 00:47 編集

根本的に、要素数が少ないので計算量やメモリの使用量のチューニングは意味を成しません。この問題のボトルネックは明らかに入出力を伴う比較関数であり、それをいかに減らすかが鍵です。また最適化すべきは大量の入力ではなく小数の入力に対してです。 プログラムを改良するならば、一つのアルゴリズムに固執せず最適なアルゴリズムを選択することをまず考えるべきでしょうし、ボトルネックがどこにあるのかを最重要視しなければならないと思います。 たとえば Ajax であれば、サーバーと百回やり取りする代わりに計算量が O(n) で済むアルゴリズムと、サーバーとのやり取りは一回で済むが計算量が O(n^2) であるアルゴリズムでは、後者を選ぶ方が良い選択である場合も多々あります。
majiponi

2020/05/16 02:32

今、5!=120個の置換1つ1つで試して、教わったアルゴリズムを試しているのですが、15432の場合、比較回数が1-5,5-4,5-3,5-2,1-4,4-3,4-2,1-3,1-2の10回となってしまったのですが、私は何か勘違いをしているのでしょうか…?
Zuishin

2020/05/16 02:57

いえ、どうも私がボケていたようです。考え直します。
guest

0

チュートリアルの問題を解いたのですが、

始めに、Practice-Bは初心者向きの問題では無いです。
(こんなところにこんな問題を置くなという運営に対する文句もありますが…)

もっと簡潔な書き方はないか?

簡潔さだけに関して言えば、n == 5 のケースを除いて、std::stable_sort を使えば、
マージソートを自前で実装する必要が無いと思います。

merge については、現状以下のような手順になっているかと思いますが、

  1. tmpにコピー
  2. tmpの先頭から、比較を行いながら入力の配列に書き込んでいく

以下のようにすれば、std::distance は消せると思います。

  1. 入力配列の先頭から、比較を行いながらtmpに書き込んでいく
  2. tmpから入力の配列にコピー。 std::copy が使えます。

マージのアルゴリズムを改良できないか、in-placeにできないか? (log(Combination(2n, n))=O(n)?)

in-place なソートアルゴリズムと言えば、ヒープソートが有名です。
マージソート同様、比較回数がO(nlogn)で抑えられています。
マージソートでinplaceかつ簡潔に記述できるアルゴリズムはよく知らないです。

n=5の場合のチューニングをコンパイル時に自動でさせる方法はないか?

これも分かりません。すみません。
Ratedの競技プログラミングコンテンストでこのようなコーナーケースが出るとは思えないですが、
ソースコードを出力するプログラムを別途書くのが一般的ではないでしょうか。

投稿2020/05/15 17:29

maai

総合スコア463

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問