前提・実現したいこと
実現したいこと:クイックソートが完全に完了するまでの過程を、一回のキーボード入力で並び替えを一工程ずつ可視化できるようにしたい。
※一回のキーボード入力でクイックソートを一括で完全に完了するプログラムは既にできています。
現在Javascriptを用いてクイックソートの可視化を行えるプログラムを作っています。
現状できているプログラムは以下の用途で動くようなものになっています。
1). キーボードの1~5を入力すると、それに応じたモデルがページ上に表示されて横一列に並ぶ。数字が小さいモデルほどそれに該当するモデルの大きさも小さい。
2). 例えばキーボードで3421523135と入力されたら、各々のモデルがその順番で横一列に並ぶ。そしてこれをいわゆるクイックソートを実行する前の未整列の配列とする。
3). エンターキーを入力するとこの配列に一括でクイックソートが実行され、2).の配列は瞬時に1122333455という順で整列する。同じくページ上に表示されているモデルたちも昇順で表示されるようになる。
ここまでは完成済
ここで上述の通り、この3).における並び替えを一括ではなく一工程ずつ可視化できるようにしたいです。
4). キーボードのA(キーコード65)を入力する度に、一工程ずつ並び替えを可視化する。
5). すべての工程が終了、つまり3).と同じ状態になった時、並び替えを終了(trace end)と宣言する。
以下のプログラムに何が足りないのか、何を修正するべきかなど、有識者の方なにかご教授いただければ幸いです。
発生している問題・エラーメッセージ
4). の並び替えが頻繁におかしな挙動を取る。
5). 4).で仮に上手く並び替えができたとしても終了(trace end)と宣言されない時がある。そもそも変な並び順でtrace endと宣言されることが多い。
下記の動画を見ていただければ分かりやすいかと...
https://youtu.be/aezBM7t3OzE
コード
// 起動されたときに呼ばれる関数を登録する window.addEventListener('load', () => { //まずステージを整える initialize(); }); function initialize() { // レンダラーを作成 const renderer = new THREE.WebGLRenderer({ canvas: document.querySelector('#canvas'), antialias: true }); // シーンを作成 const scene = new THREE.Scene(); /* var clock = new THREE.Clock();*/ // カメラを作成 const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(7, 0, 10); //camera.lookAt(new THREE.Vector3(0, 0, 0)); // Load GLTF or GLB var loader = new THREE.GLTFLoader(); var count = 0; url1 = 'img/1.gltf'; url2 = 'img/2.gltf'; url3 = 'img/3.gltf'; url4 = 'img/4.gltf'; url5 = 'img/5.gltf'; let urlList = [ url1,url2,url3,url4,url5, ]; //テクスチャをガンマ補正 renderer.gammaOutput = true; renderer.gammaFactor = 2.2; // ライト追加 hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x4169e1, 1.8); scene.add(hemisphereLight); var directionalLight = new THREE.DirectionalLight(0xFFFFFF, 1.0); // 光源色と光強度を指定して生成 directionalLight.position.set(20, 20, 100); // 光源位置を設定 scene.add(directionalLight); // シーンに追加 // フィールドサイズ追加 // 高さが全部入るように調整 // 初回実行 loop(); onResize(); // リサイズイベント発生で実行 window.addEventListener('resize', onResize); function onResize() { // サイズを取得 const w = window.innerWidth; const h = window.innerHeight; // 描画サイズ renderer.setSize(w, h); // ピクセル比 renderer.setPixelRatio(window.devicePixelRatio); // 空間の背景色 //0xから始まる16進数の色コード renderer.setClearColor(new THREE.Color(0xEEEEEE)); // カメラのアスペクト比を正す camera.aspect = w / h; camera.updateProjectionMatrix(); // レンダリング renderer.render(scene, camera); } let i = 0; let model = []; model[i] = null; let qsortCnt = 0; let qsortQueue = []; let mb = []; var s1 = 0.1; var s2 = 0.2; var s3 = 0.3; var s4 = 0.4; var s5 = 0.5; let sSize = [ s1,s2,s3,s4,s5, ]; function loop() { requestAnimationFrame(loop); document.body.onkeydown = (e) => { // キーボードが押された場合 switch (e.keyCode) { case 49: //1キー modeladd(0); qsortCnt = 0; return false; case 50: //2キー modeladd(1); qsortCnt = 0; return false; case 51: // 3キー modeladd(2); qsortCnt = 0; return false; case 52: // 4キー modeladd(3); qsortCnt = 0; return false; case 53: // 5キー modeladd(4); qsortCnt = 0; return false; case 13: //Enterキー (一括クイックソート) quickSort(0,mb.length-1); for (var si = 0; si < i + 1; si++) { model[si].position.set(si, 0, -10); } return false; case 65: //Aキー (1つ1つクイックソート) if(qsortCnt == 0){ while(qsortQueue.length){ qsortQueue.shift(); } console.log(qsortQueue); quickSortV2(0,mb.length-1); qsortCnt = 1; console.log("qsortQueue"); console.log(qsortQueue); console.log("end"); } if(qsortQueue.length){ let tmpData = qsortQueue.shift(); // オブジェクトの削除 for(var sj = 0;sj < model.length;sj++){ scene.remove(model[sj]); } while(model.length)model.shift(); // オブジェクトの新規作成 i=0; for(var sj = 0;sj < tmpData.length; sj++){ modeladd(tmpData[sj]); } } else{ console.log("trace end"); qsortCnt = 0; } return false; } }; renderer.render(scene, camera); if (count == 1) { /*if(i==XX){ //指定のモデル個数を超えた際追加不可 }*/ } } function modeladd(x){ loader.load( urlList[x], function (gltf) { model[i] = gltf.scene; model[i].scale.set(sSize[x], sSize[x], sSize[x]); model[i].position.set(i, 0, -10); scene.add(model[i]); count = 1; mb[i] = x; i += 1; }, ); } //クイックソート関数 function quickSort(startID,endID){ //範囲の間にある値をピポットに設定する var pivot = mb[Math.floor((startID + endID)/2)]; //引数を左端、右端として変数にいれる var left = startID; var right = endID; //ピポットより小さい値を左側へ、大きい値を右側へ分割する while(true){ //leftの値がpivotより小さければleftを一つ右へ移動する while(mb[left]<pivot){ left++; } //rightの値がpivotより小さければrightを一つ左へ移動する while(pivot<mb[right]){ right--; } //leftとrightの値がぶつかったら、そこでグループ分けの処理を止める。 if(right <= left){ break; } //rightとrightの値がぶつかっていない場合、leftとrightを交換 //交換後にleftを後ろへ、rightを前へ一つ移動する var tmp =mb[left]; mb[left] =mb[right]; mb[right] =tmp; var tmp2 = model[left]; model[left] = model[right]; model[right] = tmp2; left++; right--; console.log(mb); } //左側に分割できるデータがある場合、quickSort関数を呼び出して再帰的に処理を繰り返す。 if(startID < left-1){ quickSort(startID,left-1); } //右側に分割できるデータがある場合、quickSort関数を呼び出して再帰的に処理を繰り返す。 if(right+1 < endID){ quickSort(right+1,endID); } } //クイックソート関数 function quickSortV2(startID,endID){ //範囲の間にある値をピポットに設定する var pivot = mb[Math.floor((startID + endID)/2)]; //引数を左端、右端として変数にいれる var left = startID; var right = endID; //ピポットより小さい値を左側へ、大きい値を右側へ分割する while(true){ //leftの値がpivotより小さければleftを一つ右へ移動する while(mb[left]<pivot){ left++; } //rightの値がpivotより小さければrightを一つ左へ移動する while(pivot<mb[right]){ right--; } //leftとrightの値がぶつかったら、そこでグループ分けの処理を止める。 if(right <= left){ break; } //rightとrightの値がぶつかっていない場合、leftとrightを交換 //交換後にleftを後ろへ、rightを前へ一つ移動する var tmp =mb[left]; mb[left] =mb[right]; mb[right] =tmp; var tmp2 = model[left]; model[left] = model[right]; model[right] = tmp2; left++; right--; qsortQueue.push(mb); } //左側に分割できるデータがある場合、quickSort関数を呼び出して再帰的に処理を繰り返す。 if(startID < left-1){ quickSortV2(startID,left-1); } //右側に分割できるデータがある場合、quickSort関数を呼び出して再帰的に処理を繰り返す。 if(right+1 < endID){ quickSortV2(right+1,endID); } } }
参考文献
https://qiita.com/may88seiji/items/a08504d472b5cec5f916
https://algorithm-visualizer.org/divide-and-conquer/quicksort
補足情報
使用環境:VSCode
ブラウザ:Googlechrome
あなたの回答
tips
プレビュー