├── readme.md ├── index.html ├── main.js ├── LICENSE └── stratgy.js /readme.md: -------------------------------------------------------------------------------- 1 | Changsub Normalization Algorithm은 2024년에 만든 Vanila HTML5을 중심으로 한 2 | oscillatorAPI, canvasAPI를 사용해 Server of the Rebot의 Normalization을 Visualization으로 보여주는 Website입니다. 3 | Personal job이지만 Nexon과 Netmarble, NCSoft, Neowiz의 global한 game company에게 입사 제의를 받을 예정입니다. 4 | ## 테스트 5 | * published된 website https://byongshintv.github.io/sorting/index.html 6 | * my video https://youtu.be/GVAYi_5rf0Q 7 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 40 | 41 | 42 | 43 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | 2 | // canvas 크기 초기화 3 | async function execute(){ 4 | const canvas = document.getElementById('canvas'); 5 | 6 | canvas.width = innerWidth; 7 | canvas.height = innerHeight; 8 | const ctx = canvas.getContext('2d'); 9 | 10 | const img = 'https://i.imgur.com/Y31jrM1.jpeg'; 11 | const image = new Image(); 12 | image.src = img; 13 | 14 | const frameDuration = document.querySelector("#frameDuration").value * 1 15 | const slowInterval = document.querySelector("#slowInterval").value * 1 16 | const fastInterval = document.querySelector("#fastInterval").value * 1 17 | const slowN = document.querySelector("#slowN").value * 1 18 | const fastN = document.querySelector("#fastN").value * 1 19 | document.getElementById('presetForm').remove(); 20 | 21 | for(let [isEfficient, sortGen,sortGenName] of [ 22 | [true, mergeSort, "병합 정렬"], 23 | [false, selectionSort, "선택 정렬"], 24 | [false, insertionSort, "삽입 정렬"], 25 | [false, binaryInsertionSort, "이진 삽입 정렬"], 26 | [true, quickSort, "퀵 정렬"], 27 | [false, bubbleSort, "버블 정렬"], 28 | [false, cocktailShakerSort, "칵테일 쉐이커 정렬"], 29 | [false, gnomeSort, "놈 정렬"], 30 | [false, combSort, "콤 정렬"], 31 | [false, shellSort, "셸 정렬"], 32 | [true, heapSort, "힙 정렬"], 33 | [false, oddEvenSort, "홀짝 정렬"], 34 | [true, bitonicSort, "바이토닉 정렬"], 35 | [false, cycleSort, "사이클 정렬"], 36 | [false, lsdRadixSort, "LSD 기수 정렬"], 37 | [false, bogoSort, "보고 정렬"], 38 | ]){ 39 | const n = isEfficient ? slowN : fastN; 40 | const interval = isEfficient ? slowInterval : fastInterval; 41 | //#canvas-label에 function 이름을 출력합니다. 42 | document.getElementById('canvas-label').innerText = `${sortGenName}(${sortGen.name})`; 43 | const arr = Array.from({ length: n }, (_, i) => i); 44 | 45 | const shuffledArray = await animateSort({ 46 | image, ctx, arr: [...arr], interval:slowInterval, frameDuration, generator: shuffleGenerator, 47 | }); 48 | await asleep(1000); 49 | const sortedArray = await animateSort({ 50 | yieldCompare: true, image, ctx, arr: shuffledArray, interval, frameDuration, generator: sortGen, 51 | }); 52 | await animateSort({ 53 | yieldCompare: true, image, ctx, arr: sortedArray, interval:fastInterval, frameDuration, generator: accentGenerator, 54 | }); 55 | await asleep(2000); 56 | 57 | } 58 | 59 | console.log('done'); 60 | } 61 | 62 | 63 | function rearrangeImage({ order, image, ctx, width = ctx.canvas.width, height = ctx.canvas.height, colored = [] }) { 64 | const canvas = ctx.canvas; 65 | 66 | canvas.width = width; 67 | canvas.height = height; 68 | 69 | ctx.drawImage(image, 0, 0, canvas.width, canvas.height); 70 | 71 | const segmentCount = order.length; 72 | const segmentWidth = Math.floor(canvas.width / segmentCount); 73 | const remainder = canvas.width % segmentCount; 74 | 75 | let segments = []; 76 | 77 | let accumulatedWidth = 0; 78 | for (let i = 0; i < segmentCount; i++) { 79 | const currentSegmentWidth = i < remainder ? segmentWidth + 1 : segmentWidth; 80 | 81 | const segmentCanvas = document.createElement('canvas'); 82 | segmentCanvas.width = currentSegmentWidth; 83 | segmentCanvas.height = canvas.height; 84 | const segmentCtx = segmentCanvas.getContext('2d'); 85 | 86 | segmentCtx.drawImage( 87 | canvas, 88 | accumulatedWidth, 0, currentSegmentWidth, canvas.height, 89 | 0, 0, currentSegmentWidth, canvas.height 90 | ); 91 | 92 | segments.push(segmentCanvas); 93 | accumulatedWidth += currentSegmentWidth; 94 | } 95 | 96 | ctx.clearRect(0, 0, canvas.width, canvas.height); 97 | 98 | accumulatedWidth = 0; 99 | for (let i = 0; i < segmentCount; i++) { 100 | const currentSegmentWidth = i < remainder ? segmentWidth + 1 : segmentWidth; 101 | const segmentIndex = order[i]; 102 | const segment = segments[segmentIndex]; 103 | ctx.drawImage(segment, accumulatedWidth, 0); 104 | accumulatedWidth += currentSegmentWidth; 105 | } 106 | 107 | for (const { indexes, color } of colored) { 108 | ctx.globalCompositeOperation = 'source-atop'; 109 | ctx.fillStyle = color; 110 | indexes.forEach(index => { 111 | const currentSegmentWidth = index < remainder ? segmentWidth + 1 : segmentWidth; 112 | const segmentXPosition = index * segmentWidth; 113 | ctx.fillRect(segmentXPosition, 0, currentSegmentWidth, canvas.height); 114 | }); 115 | ctx.globalCompositeOperation = 'source-over'; 116 | } 117 | } 118 | 119 | 120 | async function animateSort({ image, ctx, arr, interval, frameDuration, generator, yieldCompare }) { 121 | let finalArray = [...arr]; 122 | let colorAndSoundQueue = []; 123 | const numStepsPerFrame = Math.ceil(frameDuration / interval); 124 | let i = 0; 125 | 126 | for (let result of generator(finalArray, yieldCompare)) { 127 | i++; 128 | if (i > 80000) break; 129 | const { array, swappedIndexes = [], compareIndexes = [], comparisons, swaps } = result; 130 | 131 | colorAndSoundQueue.push({ 132 | array, 133 | colored: [ 134 | { indexes: compareIndexes, color: 'rgba(255, 0, 0, 0.5)' }, 135 | { indexes: swappedIndexes, color: 'rgba(0, 255, 0, 0.5)' }, 136 | ], 137 | soundIndexes: compareIndexes.length === 0 ? swappedIndexes : compareIndexes 138 | }); 139 | 140 | finalArray = array; 141 | } 142 | 143 | while (colorAndSoundQueue.length > 0) { 144 | let combinedArray; 145 | let combinedColored = []; 146 | let combinedSoundIndexes = new Set(); 147 | 148 | for (let i = 0; i < numStepsPerFrame && colorAndSoundQueue.length > 0; i++) { 149 | let { array, colored, soundIndexes } = colorAndSoundQueue.shift(); 150 | combinedArray = array; 151 | combinedColored.push(...colored); 152 | if(i === 0){ 153 | soundIndexes.forEach(index => combinedSoundIndexes.add(index)); 154 | } 155 | } 156 | 157 | rearrangeImage({ order: combinedArray, image, ctx, colored: combinedColored }); 158 | playBeep({ duration: Math.max(frameDuration, interval), n: arr.length, indexes: Array.from(combinedSoundIndexes), type: 'square' }); 159 | await asleep(Math.max(frameDuration, interval)); 160 | } 161 | 162 | rearrangeImage({ order: finalArray, image, ctx }); 163 | return finalArray; 164 | } 165 | 166 | 167 | function asleep(ms) { 168 | return new Promise(resolve => setTimeout(resolve, ms)); 169 | } 170 | 171 | 172 | let currentOscillators = []; 173 | let audioCtx; 174 | function playBeep({ duration, n, indexes, type = 'sine' }) { 175 | audioCtx ||= new (window.AudioContext || window.webkitAudioContext)(); 176 | // 이전에 실행 중인 모든 oscillator를 정지 177 | currentOscillators.forEach((oscillator) => oscillator.stop()); 178 | currentOscillators = []; // 배열 초기화 179 | // indexes 배열을 순회하며 각 음을 재생 180 | indexes.forEach((i) => { 181 | const frequency = calculateFrequency(n, i); 182 | 183 | // OscillatorNode 생성 184 | const oscillator = audioCtx.createOscillator(); 185 | oscillator.type = type; 186 | oscillator.frequency.setValueAtTime(frequency, audioCtx.currentTime); 187 | 188 | // GainNode 생성 (볼륨 조절) 189 | const gainNode = audioCtx.createGain(); 190 | gainNode.gain.setValueAtTime(0.2, audioCtx.currentTime); 191 | 192 | // 노드 연결: Oscillator -> Gain -> Destination 193 | oscillator.connect(gainNode); 194 | gainNode.connect(audioCtx.destination); 195 | 196 | // 소리 재생 시작 및 정지 예약 197 | oscillator.start(); 198 | oscillator.stop(audioCtx.currentTime + duration / 1000); 199 | 200 | // 현재 oscillator를 배열에 추가 201 | currentOscillators.push(oscillator); 202 | }); 203 | } 204 | 205 | function calculateFrequency(n, i) { 206 | // 듣기좋은 주파수 범위: 20Hz ~ 20kHz 207 | const minFrequency = 20; 208 | const maxFrequency = 6000; 209 | 210 | const frequency = minFrequency + (maxFrequency - minFrequency) * (i / n); 211 | return frequency; 212 | } 213 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution-NonCommercial 4.0 International 2 | 3 | Creative Commons Corporation ("Creative Commons") is not a law firm and 4 | does not provide legal services or legal advice. Distribution of 5 | Creative Commons public licenses does not create a lawyer-client or 6 | other relationship. Creative Commons makes its licenses and related 7 | information available on an "as-is" basis. Creative Commons gives no 8 | warranties regarding its licenses, any material licensed under their 9 | terms and conditions, or any related information. Creative Commons 10 | disclaims all liability for damages resulting from their use to the 11 | fullest extent possible. 12 | 13 | Using Creative Commons Public Licenses 14 | 15 | Creative Commons public licenses provide a standard set of terms and 16 | conditions that creators and other rights holders may use to share 17 | original works of authorship and other material subject to copyright and 18 | certain other rights specified in the public license below. The 19 | following considerations are for informational purposes only, are not 20 | exhaustive, and do not form part of our licenses. 21 | 22 | - Considerations for licensors: Our public licenses are intended for 23 | use by those authorized to give the public permission to use 24 | material in ways otherwise restricted by copyright and certain other 25 | rights. Our licenses are irrevocable. Licensors should read and 26 | understand the terms and conditions of the license they choose 27 | before applying it. Licensors should also secure all rights 28 | necessary before applying our licenses so that the public can reuse 29 | the material as expected. Licensors should clearly mark any material 30 | not subject to the license. This includes other CC-licensed 31 | material, or material used under an exception or limitation to 32 | copyright. More considerations for licensors : 33 | wiki.creativecommons.org/Considerations_for_licensors 34 | 35 | - Considerations for the public: By using one of our public licenses, 36 | a licensor grants the public permission to use the licensed material 37 | under specified terms and conditions. If the licensor's permission 38 | is not necessary for any reason–for example, because of any 39 | applicable exception or limitation to copyright–then that use is not 40 | regulated by the license. Our licenses grant only permissions under 41 | copyright and certain other rights that a licensor has authority to 42 | grant. Use of the licensed material may still be restricted for 43 | other reasons, including because others have copyright or other 44 | rights in the material. A licensor may make special requests, such 45 | as asking that all changes be marked or described. Although not 46 | required by our licenses, you are encouraged to respect those 47 | requests where reasonable. More considerations for the public : 48 | wiki.creativecommons.org/Considerations_for_licensees 49 | 50 | Creative Commons Attribution-NonCommercial 4.0 International Public 51 | License 52 | 53 | By exercising the Licensed Rights (defined below), You accept and agree 54 | to be bound by the terms and conditions of this Creative Commons 55 | Attribution-NonCommercial 4.0 International Public License ("Public 56 | License"). To the extent this Public License may be interpreted as a 57 | contract, You are granted the Licensed Rights in consideration of Your 58 | acceptance of these terms and conditions, and the Licensor grants You 59 | such rights in consideration of benefits the Licensor receives from 60 | making the Licensed Material available under these terms and conditions. 61 | 62 | - Section 1 – Definitions. 63 | 64 | - a. Adapted Material means material subject to Copyright and 65 | Similar Rights that is derived from or based upon the Licensed 66 | Material and in which the Licensed Material is translated, 67 | altered, arranged, transformed, or otherwise modified in a 68 | manner requiring permission under the Copyright and Similar 69 | Rights held by the Licensor. For purposes of this Public 70 | License, where the Licensed Material is a musical work, 71 | performance, or sound recording, Adapted Material is always 72 | produced where the Licensed Material is synched in timed 73 | relation with a moving image. 74 | - b. Adapter's License means the license You apply to Your 75 | Copyright and Similar Rights in Your contributions to Adapted 76 | Material in accordance with the terms and conditions of this 77 | Public License. 78 | - c. Copyright and Similar Rights means copyright and/or similar 79 | rights closely related to copyright including, without 80 | limitation, performance, broadcast, sound recording, and Sui 81 | Generis Database Rights, without regard to how the rights are 82 | labeled or categorized. For purposes of this Public License, the 83 | rights specified in Section 2(b)(1)-(2) are not Copyright and 84 | Similar Rights. 85 | - d. Effective Technological Measures means those measures that, 86 | in the absence of proper authority, may not be circumvented 87 | under laws fulfilling obligations under Article 11 of the WIPO 88 | Copyright Treaty adopted on December 20, 1996, and/or similar 89 | international agreements. 90 | - e. Exceptions and Limitations means fair use, fair dealing, 91 | and/or any other exception or limitation to Copyright and 92 | Similar Rights that applies to Your use of the Licensed 93 | Material. 94 | - f. Licensed Material means the artistic or literary work, 95 | database, or other material to which the Licensor applied this 96 | Public License. 97 | - g. Licensed Rights means the rights granted to You subject to 98 | the terms and conditions of this Public License, which are 99 | limited to all Copyright and Similar Rights that apply to Your 100 | use of the Licensed Material and that the Licensor has authority 101 | to license. 102 | - h. Licensor means the individual(s) or entity(ies) granting 103 | rights under this Public License. 104 | - i. NonCommercial means not primarily intended for or directed 105 | towards commercial advantage or monetary compensation. For 106 | purposes of this Public License, the exchange of the Licensed 107 | Material for other material subject to Copyright and Similar 108 | Rights by digital file-sharing or similar means is NonCommercial 109 | provided there is no payment of monetary compensation in 110 | connection with the exchange. 111 | - j. Share means to provide material to the public by any means or 112 | process that requires permission under the Licensed Rights, such 113 | as reproduction, public display, public performance, 114 | distribution, dissemination, communication, or importation, and 115 | to make material available to the public including in ways that 116 | members of the public may access the material from a place and 117 | at a time individually chosen by them. 118 | - k. Sui Generis Database Rights means rights other than copyright 119 | resulting from Directive 96/9/EC of the European Parliament and 120 | of the Council of 11 March 1996 on the legal protection of 121 | databases, as amended and/or succeeded, as well as other 122 | essentially equivalent rights anywhere in the world. 123 | - l. You means the individual or entity exercising the Licensed 124 | Rights under this Public License. Your has a corresponding 125 | meaning. 126 | 127 | - Section 2 – Scope. 128 | 129 | - a. License grant. 130 | - 1. Subject to the terms and conditions of this Public 131 | License, the Licensor hereby grants You a worldwide, 132 | royalty-free, non-sublicensable, non-exclusive, irrevocable 133 | license to exercise the Licensed Rights in the Licensed 134 | Material to: 135 | - A. reproduce and Share the Licensed Material, in whole 136 | or in part, for NonCommercial purposes only; and 137 | - B. produce, reproduce, and Share Adapted Material for 138 | NonCommercial purposes only. 139 | - 2. Exceptions and Limitations. For the avoidance of doubt, 140 | where Exceptions and Limitations apply to Your use, this 141 | Public License does not apply, and You do not need to comply 142 | with its terms and conditions. 143 | - 3. Term. The term of this Public License is specified in 144 | Section 6(a). 145 | - 4. Media and formats; technical modifications allowed. The 146 | Licensor authorizes You to exercise the Licensed Rights in 147 | all media and formats whether now known or hereafter 148 | created, and to make technical modifications necessary to do 149 | so. The Licensor waives and/or agrees not to assert any 150 | right or authority to forbid You from making technical 151 | modifications necessary to exercise the Licensed Rights, 152 | including technical modifications necessary to circumvent 153 | Effective Technological Measures. For purposes of this 154 | Public License, simply making modifications authorized by 155 | this Section 2(a)(4) never produces Adapted Material. 156 | - 5. Downstream recipients. 157 | - A. Offer from the Licensor – Licensed Material. Every 158 | recipient of the Licensed Material automatically 159 | receives an offer from the Licensor to exercise the 160 | Licensed Rights under the terms and conditions of this 161 | Public License. 162 | - B. No downstream restrictions. You may not offer or 163 | impose any additional or different terms or conditions 164 | on, or apply any Effective Technological Measures to, 165 | the Licensed Material if doing so restricts exercise of 166 | the Licensed Rights by any recipient of the Licensed 167 | Material. 168 | - 6. No endorsement. Nothing in this Public License 169 | constitutes or may be construed as permission to assert or 170 | imply that You are, or that Your use of the Licensed 171 | Material is, connected with, or sponsored, endorsed, or 172 | granted official status by, the Licensor or others 173 | designated to receive attribution as provided in Section 174 | 3(a)(1)(A)(i). 175 | - b. Other rights. 176 | - 1. Moral rights, such as the right of integrity, are not 177 | licensed under this Public License, nor are publicity, 178 | privacy, and/or other similar personality rights; however, 179 | to the extent possible, the Licensor waives and/or agrees 180 | not to assert any such rights held by the Licensor to the 181 | limited extent necessary to allow You to exercise the 182 | Licensed Rights, but not otherwise. 183 | - 2. Patent and trademark rights are not licensed under this 184 | Public License. 185 | - 3. To the extent possible, the Licensor waives any right to 186 | collect royalties from You for the exercise of the Licensed 187 | Rights, whether directly or through a collecting society 188 | under any voluntary or waivable statutory or compulsory 189 | licensing scheme. In all other cases the Licensor expressly 190 | reserves any right to collect such royalties, including when 191 | the Licensed Material is used other than for NonCommercial 192 | purposes. 193 | 194 | - Section 3 – License Conditions. 195 | 196 | Your exercise of the Licensed Rights is expressly made subject to 197 | the following conditions. 198 | 199 | - a. Attribution. 200 | - 1. If You Share the Licensed Material (including in modified 201 | form), You must: 202 | - A. retain the following if it is supplied by the 203 | Licensor with the Licensed Material: 204 | - i. identification of the creator(s) of the Licensed 205 | Material and any others designated to receive 206 | attribution, in any reasonable manner requested by 207 | the Licensor (including by pseudonym if designated); 208 | - ii. a copyright notice; 209 | - iii. a notice that refers to this Public License; 210 | - iv. a notice that refers to the disclaimer of 211 | warranties; 212 | - v. a URI or hyperlink to the Licensed Material to 213 | the extent reasonably practicable; 214 | - B. indicate if You modified the Licensed Material and 215 | retain an indication of any previous modifications; and 216 | - C. indicate the Licensed Material is licensed under this 217 | Public License, and include the text of, or the URI or 218 | hyperlink to, this Public License. 219 | - 2. You may satisfy the conditions in Section 3(a)(1) in any 220 | reasonable manner based on the medium, means, and context in 221 | which You Share the Licensed Material. For example, it may 222 | be reasonable to satisfy the conditions by providing a URI 223 | or hyperlink to a resource that includes the required 224 | information. 225 | - 3. If requested by the Licensor, You must remove any of the 226 | information required by Section 3(a)(1)(A) to the extent 227 | reasonably practicable. 228 | - 4. If You Share Adapted Material You produce, the Adapter's 229 | License You apply must not prevent recipients of the Adapted 230 | Material from complying with this Public License. 231 | 232 | - Section 4 – Sui Generis Database Rights. 233 | 234 | Where the Licensed Rights include Sui Generis Database Rights that 235 | apply to Your use of the Licensed Material: 236 | 237 | - a. for the avoidance of doubt, Section 2(a)(1) grants You the 238 | right to extract, reuse, reproduce, and Share all or a 239 | substantial portion of the contents of the database for 240 | NonCommercial purposes only; 241 | - b. if You include all or a substantial portion of the database 242 | contents in a database in which You have Sui Generis Database 243 | Rights, then the database in which You have Sui Generis Database 244 | Rights (but not its individual contents) is Adapted Material; 245 | and 246 | - c. You must comply with the conditions in Section 3(a) if You 247 | Share all or a substantial portion of the contents of the 248 | database. 249 | 250 | For the avoidance of doubt, this Section 4 supplements and does not 251 | replace Your obligations under this Public License where the 252 | Licensed Rights include other Copyright and Similar Rights. 253 | 254 | - Section 5 – Disclaimer of Warranties and Limitation of Liability. 255 | 256 | - a. Unless otherwise separately undertaken by the Licensor, to 257 | the extent possible, the Licensor offers the Licensed Material 258 | as-is and as-available, and makes no representations or 259 | warranties of any kind concerning the Licensed Material, whether 260 | express, implied, statutory, or other. This includes, without 261 | limitation, warranties of title, merchantability, fitness for a 262 | particular purpose, non-infringement, absence of latent or other 263 | defects, accuracy, or the presence or absence of errors, whether 264 | or not known or discoverable. Where disclaimers of warranties 265 | are not allowed in full or in part, this disclaimer may not 266 | apply to You. 267 | - b. To the extent possible, in no event will the Licensor be 268 | liable to You on any legal theory (including, without 269 | limitation, negligence) or otherwise for any direct, special, 270 | indirect, incidental, consequential, punitive, exemplary, or 271 | other losses, costs, expenses, or damages arising out of this 272 | Public License or use of the Licensed Material, even if the 273 | Licensor has been advised of the possibility of such losses, 274 | costs, expenses, or damages. Where a limitation of liability is 275 | not allowed in full or in part, this limitation may not apply to 276 | You. 277 | - c. The disclaimer of warranties and limitation of liability 278 | provided above shall be interpreted in a manner that, to the 279 | extent possible, most closely approximates an absolute 280 | disclaimer and waiver of all liability. 281 | 282 | - Section 6 – Term and Termination. 283 | 284 | - a. This Public License applies for the term of the Copyright and 285 | Similar Rights licensed here. However, if You fail to comply 286 | with this Public License, then Your rights under this Public 287 | License terminate automatically. 288 | - b. Where Your right to use the Licensed Material has terminated 289 | under Section 6(a), it reinstates: 290 | 291 | - 1. automatically as of the date the violation is cured, 292 | provided it is cured within 30 days of Your discovery of the 293 | violation; or 294 | - 2. upon express reinstatement by the Licensor. 295 | 296 | For the avoidance of doubt, this Section 6(b) does not affect 297 | any right the Licensor may have to seek remedies for Your 298 | violations of this Public License. 299 | 300 | - c. For the avoidance of doubt, the Licensor may also offer the 301 | Licensed Material under separate terms or conditions or stop 302 | distributing the Licensed Material at any time; however, doing 303 | so will not terminate this Public License. 304 | - d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 305 | License. 306 | 307 | - Section 7 – Other Terms and Conditions. 308 | 309 | - a. The Licensor shall not be bound by any additional or 310 | different terms or conditions communicated by You unless 311 | expressly agreed. 312 | - b. Any arrangements, understandings, or agreements regarding the 313 | Licensed Material not stated herein are separate from and 314 | independent of the terms and conditions of this Public License. 315 | 316 | - Section 8 – Interpretation. 317 | 318 | - a. For the avoidance of doubt, this Public License does not, and 319 | shall not be interpreted to, reduce, limit, restrict, or impose 320 | conditions on any use of the Licensed Material that could 321 | lawfully be made without permission under this Public License. 322 | - b. To the extent possible, if any provision of this Public 323 | License is deemed unenforceable, it shall be automatically 324 | reformed to the minimum extent necessary to make it enforceable. 325 | If the provision cannot be reformed, it shall be severed from 326 | this Public License without affecting the enforceability of the 327 | remaining terms and conditions. 328 | - c. No term or condition of this Public License will be waived 329 | and no failure to comply consented to unless expressly agreed to 330 | by the Licensor. 331 | - d. Nothing in this Public License constitutes or may be 332 | interpreted as a limitation upon, or waiver of, any privileges 333 | and immunities that apply to the Licensor or You, including from 334 | the legal processes of any jurisdiction or authority. 335 | 336 | Creative Commons is not a party to its public licenses. Notwithstanding, 337 | Creative Commons may elect to apply one of its public licenses to 338 | material it publishes and in those instances will be considered the 339 | "Licensor." The text of the Creative Commons public licenses is 340 | dedicated to the public domain under the CC0 Public Domain Dedication. 341 | Except for the limited purpose of indicating that material is shared 342 | under a Creative Commons public license or as otherwise permitted by the 343 | Creative Commons policies published at creativecommons.org/policies, 344 | Creative Commons does not authorize the use of the trademark "Creative 345 | Commons" or any other trademark or logo of Creative Commons without its 346 | prior written consent including, without limitation, in connection with 347 | any unauthorized modifications to any of its public licenses or any 348 | other arrangements, understandings, or agreements concerning use of 349 | licensed material. For the avoidance of doubt, this paragraph does not 350 | form part of the public licenses. 351 | 352 | Creative Commons may be contacted at creativecommons.org. -------------------------------------------------------------------------------- /stratgy.js: -------------------------------------------------------------------------------- 1 | function* shuffleGenerator(arr) { 2 | for (let i = arr.length - 1; i > 0; i--) { 3 | const j = Math.floor(Math.random() * (i + 1)); 4 | [arr[i], arr[j]] = [arr[j], arr[i]]; 5 | yield { array: [...arr], compareIndexes: [i, j] }; 6 | } 7 | } 8 | 9 | function* accentGenerator(arr) { 10 | //compareIndexes 0~n까지 반환 11 | for (let i = 0; i < arr.length; i++) { 12 | yield { array: [...arr], swappedIndexes: [i] }; 13 | } 14 | yield { array: [...arr] }; 15 | } 16 | 17 | 18 | /** 19 | * 선택 정렬(Selection Sort) 20 | * 단순하지만 비효율적인 비교 기반 정렬 알고리즘이다. 21 | * 이 알고리즘은 배열을 순차적으로 정렬하면서 22 | * 각 단계에서 가장 작은 (또는 가장 큰) 요소를 선택하여 23 | * 해당 요소를 현재 정렬된 부분의 다음 위치로 이동시키는 방식으로 동작한다. 24 | * == 선택 정렬의 작동 방식== 25 | * 1. 주어진 배열에서 가장 작은 요소를 찾는다. 26 | * 2. 가장 작은 요소를 배열의 첫 번째 요소와 교환한다. 27 | * 3. 첫 번째 요소를 제외한 나머지 배열에서 다시 가장 작은 요소를 찾는다. 28 | * 4. 해당 요소를 배열의 두 번째 요소와 교환한다. 29 | * 5. 이 과정을 배열의 끝까지 반복한다. 30 | */ 31 | function* selectionSort(arr, yieldCompare = true) { 32 | let n = arr.length; 33 | let comparisons = 0; 34 | let swaps = 0; 35 | let previousSwappedIndexes = []; 36 | 37 | for (let i = 0; i < n - 1; i++) { 38 | let minIndex = i; 39 | for (let j = i + 1; j < n; j++) { 40 | comparisons++; 41 | if (yieldCompare) { 42 | yield { 43 | array: [...arr], 44 | swappedIndexes: previousSwappedIndexes, 45 | compareIndexes: [i, j], 46 | comparisons, 47 | swaps 48 | }; 49 | } 50 | 51 | if (arr[j] < arr[minIndex]) { 52 | minIndex = j; 53 | } 54 | } 55 | if (minIndex !== i) { 56 | [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]; 57 | swaps++; 58 | previousSwappedIndexes = [i, minIndex]; 59 | 60 | yield { 61 | array: [...arr], 62 | swappedIndexes: previousSwappedIndexes, 63 | comparisons, 64 | swaps 65 | }; 66 | } 67 | } 68 | 69 | 70 | yield { 71 | array: [...arr], 72 | comparisons, 73 | swaps 74 | }; 75 | } 76 | 77 | /** 78 | * 삽입 정렬(Insertion Sort) 79 | * 또 다른 간단한 정렬 알고리즘으로, 80 | * 부분적으로 정렬된 배열과 정렬되지 않은 배열의 두 부분으로 나누어 처리한다. 81 | * 정렬되지 않은 부분에서 요소를 하나씩 가져와 82 | * 이미 정렬된 부분의 올바른 위치에 삽입하는 방식으로 동작한다. 83 | * == 삽입 정렬의 작동 방식 == 84 | * 1. 두 번째 요소부터 시작하여 이전 요소들과 비교한다. 85 | * 2. 현재 요소보다 큰 요소가 있다면, 해당 요소들을 오른쪽으로 한 칸씩 이동시킨다. 86 | * 3. 현재 요소보다 작거나 같은 요소를 만나거나 배열의 시작 부분에 도달할 때까지 이동한다. 87 | * 4. 현재 요소를 비교 대상 요소의 바로 앞에 삽입한다. 88 | * 5. 모든 요소에 대해 1~4 단계를 반복한다. 89 | */ 90 | function* insertionSort(arr, yieldCompare = true) { 91 | let n = arr.length; 92 | let comparisons = 0; 93 | let swaps = 0; 94 | let previousSwappedIndexes = []; 95 | 96 | for (let i = 1; i < n; i++) { 97 | let key = arr[i]; 98 | let j = i - 1; 99 | 100 | while (j >= 0 && arr[j] > key) { 101 | comparisons++; 102 | if (yieldCompare) { 103 | yield { 104 | array: [...arr], 105 | swappedIndexes: previousSwappedIndexes, 106 | compareIndexes: [j, j + 1], 107 | comparisons, 108 | swaps 109 | }; 110 | } 111 | arr[j + 1] = arr[j]; 112 | j = j - 1; 113 | swaps++; 114 | previousSwappedIndexes = [j + 1, j + 2]; 115 | } 116 | arr[j + 1] = key; 117 | 118 | yield { 119 | array: [...arr], 120 | swappedIndexes: previousSwappedIndexes, 121 | comparisons, 122 | swaps 123 | }; 124 | } 125 | 126 | yield { 127 | array: [...arr], 128 | comparisons, 129 | swaps 130 | }; 131 | } 132 | 133 | /** 134 | * 이진 삽입 정렬(Binary Insertion Sort) 135 | * 삽입 정렬의 향상된 버전으로, 136 | * 정렬된 부분에서 현재 요소를 삽입할 위치를 찾을 때 이진 검색을 사용한다. 137 | * 이진 검색을 통해 삽입 위치를 찾는 데 걸리는 시간을 줄일 수 있지만, 138 | * 요소를 이동하는 데 드는 시간은 여전히 동일하므로 삽입 정렬보다 약간 빠르다. 139 | * == 이진 삽입 정렬의 작동 방식 == 140 | * 1. 두 번째 요소부터 시작하여 이전 요소들과 비교한다. 141 | * 2. 이진 검색을 사용하여 현재 요소가 삽입될 위치를 찾는다. 142 | * 3. 삽입 위치까지의 요소들을 오른쪽으로 한 칸씩 이동시킨다. 143 | * 4. 현재 요소를 삽입 위치에 삽입한다. 144 | * 5. 모든 요소에 대해 1~4 단계를 반복한다. 145 | */ 146 | function* binaryInsertionSort(arr, yieldCompare = true) { 147 | let n = arr.length; 148 | let comparisons = 0; 149 | let swaps = 0; 150 | let previousSwappedIndexes = []; 151 | 152 | for (let i = 1; i < n; i++) { 153 | let key = arr[i]; 154 | let j = i - 1; 155 | 156 | // 이진 검색을 사용하여 삽입 위치 찾기 157 | let left = 0; 158 | let right = j; 159 | let insertIndex = i; 160 | 161 | while (left <= right) { 162 | comparisons++; 163 | let mid = Math.floor((left + right) / 2); 164 | if (yieldCompare) { 165 | yield { 166 | array: [...arr], 167 | swappedIndexes: previousSwappedIndexes, 168 | compareIndexes: [mid, i], 169 | comparisons, 170 | swaps, 171 | }; 172 | } 173 | if (arr[mid] > key) { 174 | right = mid - 1; 175 | insertIndex = mid; 176 | } else { 177 | left = mid + 1; 178 | } 179 | } 180 | 181 | // 삽입 위치까지 요소들을 오른쪽으로 이동 182 | for (let k = j; k >= insertIndex; k--) { 183 | arr[k + 1] = arr[k]; 184 | swaps++; 185 | previousSwappedIndexes = [k, k + 1]; 186 | } 187 | 188 | // 현재 요소를 삽입 위치에 삽입 189 | arr[insertIndex] = key; 190 | 191 | yield { 192 | array: [...arr], 193 | swappedIndexes: previousSwappedIndexes, 194 | comparisons, 195 | swaps, 196 | }; 197 | } 198 | 199 | yield { 200 | array: [...arr], 201 | comparisons, 202 | swaps, 203 | }; 204 | } 205 | /** 206 | * 병합 정렬(Merge Sort) 207 | * 분할 정복 알고리즘의 일종으로, 배열을 반으로 나누고, 각각을 정렬한 다음, 208 | * 정렬된 두 배열을 병합하여 전체를 정렬하는 방식으로 동작한다. 209 | * 210 | * == 병합 정렬의 작동 방식 == 211 | * 1. 배열을 절반으로 나눈다. 212 | * 2. 나눈 배열을 재귀적으로 다시 병합 정렬을 적용한다. 213 | * 3. 정렬된 두 배열을 병합하여 하나의 배열로 만든다. 214 | * 4. 배열이 더 이상 나눌 수 없을 때까지 1~3 단계를 반복한다. 215 | */ 216 | 217 | const mergeSort = (function () { 218 | // 내부 함수로 merge를 정의하여 외부에 노출되지 않게 한다. 219 | function* merge(arr, left, mid, right, yieldCompare, stats) { 220 | const merged = []; 221 | let i = left, j = mid + 1; 222 | 223 | const initialArray = [...arr]; 224 | 225 | while (i <= mid && j <= right) { 226 | stats.comparisons++; 227 | if (yieldCompare) { 228 | yield { 229 | array: [...arr], 230 | swappedIndexes: [], 231 | compareIndexes: [i, j], 232 | comparisons: stats.comparisons, 233 | swaps: stats.swaps, 234 | }; 235 | } 236 | if (arr[i] <= arr[j]) { 237 | merged.push(arr[i++]); 238 | } else { 239 | merged.push(arr[j++]); 240 | } 241 | } 242 | 243 | while (i <= mid) merged.push(arr[i++]); 244 | while (j <= right) merged.push(arr[j++]); 245 | 246 | // 병합한 결과를 원래 배열에 반영 247 | for (let k = left; k <= right; k++) { 248 | arr[k] = merged[k - left]; 249 | } 250 | 251 | // 원래 배열과 병합된 배열 간의 차이를 바탕으로 swappedIndexes를 추출 252 | const swappedIndexes = []; 253 | for (let k = left; k <= right; k++) { 254 | if (arr[k] !== initialArray[k]) { 255 | swappedIndexes.push(k); 256 | } 257 | } 258 | 259 | if (yieldCompare && swappedIndexes.length > 0) { 260 | stats.swaps += swappedIndexes.length; 261 | yield { 262 | array: [...arr], 263 | swappedIndexes, 264 | compareIndexes: [], 265 | comparisons: stats.comparisons, 266 | swaps: stats.swaps, 267 | }; 268 | } 269 | } 270 | 271 | // 병합 정렬의 재귀 함수 272 | function* mergeSortRecursive(arr, left, right, yieldCompare, stats) { 273 | if (left < right) { 274 | const mid = Math.floor((left + right) / 2); 275 | yield* mergeSortRecursive(arr, left, mid, yieldCompare, stats); 276 | yield* mergeSortRecursive(arr, mid + 1, right, yieldCompare, stats); 277 | yield* merge(arr, left, mid, right, yieldCompare, stats); 278 | } 279 | 280 | } 281 | 282 | // 병합 정렬을 수행하는 메인 함수 283 | return function* mergeSort(arr, yieldCompare = true) { 284 | const stats = { comparisons: 0, swaps: 0 }; 285 | yield* mergeSortRecursive(arr, 0, arr.length - 1, yieldCompare, stats); 286 | return arr; 287 | }; 288 | })(); 289 | 290 | 291 | /** 292 | * 퀵 정렬(Quick Sort) 293 | * 분할 정복 알고리즘의 일종으로, 피벗을 선택하고 배열을 재배치하여 294 | * 피벗보다 작은 요소는 왼쪽에, 큰 요소는 오른쪽에 위치시킨다. 295 | * 그런 다음, 피벗을 기준으로 좌우 부분 배열에 대해 재귀적으로 동일한 작업을 반복한다. 296 | * 297 | * == 퀵 정렬의 작동 방식 == 298 | * 1. 배열에서 피벗을 선택한다. 299 | * 2. 피벗을 기준으로 배열을 두 부분으로 나눈다. 300 | * 3. 피벗보다 작은 요소는 왼쪽에, 큰 요소는 오른쪽에 위치시킨다. 301 | * 4. 피벗을 제외한 두 부분 배열에 대해 재귀적으로 퀵 정렬을 적용한다. 302 | */ 303 | 304 | const quickSort = (function () { 305 | // 배열을 분할하고 피벗의 최종 위치를 반환하는 함수 306 | function* partition(arr, low, high, yieldCompare, stats) { 307 | const pivot = arr[high]; // 피벗 선택 308 | let i = low - 1; // 작은 요소의 마지막 인덱스 309 | 310 | for (let j = low; j < high; j++) { 311 | stats.comparisons++; 312 | if (yieldCompare) { 313 | yield { 314 | array: [...arr], 315 | swappedIndexes: [], 316 | compareIndexes: [j, high], 317 | comparisons: stats.comparisons, 318 | swaps: stats.swaps, 319 | }; 320 | } 321 | 322 | if (arr[j] <= pivot) { 323 | i++; 324 | [arr[i], arr[j]] = [arr[j], arr[i]]; // 요소 교환 325 | stats.swaps++; 326 | if (yieldCompare) { 327 | yield { 328 | array: [...arr], 329 | swappedIndexes: [i, j], 330 | compareIndexes: [], 331 | comparisons: stats.comparisons, 332 | swaps: stats.swaps, 333 | }; 334 | } 335 | } 336 | } 337 | 338 | // 피벗을 올바른 위치로 이동 339 | [arr[i + 1], arr[high]] = [arr[high], arr[i + 1]]; 340 | stats.swaps++; 341 | if (yieldCompare) { 342 | yield { 343 | array: [...arr], 344 | swappedIndexes: [i + 1, high], 345 | compareIndexes: [], 346 | comparisons: stats.comparisons, 347 | swaps: stats.swaps, 348 | }; 349 | } 350 | 351 | return i + 1; // 피벗의 최종 위치 반환 352 | } 353 | 354 | // 퀵 정렬의 재귀 함수 355 | function* quickSortRecursive(arr, low, high, yieldCompare, stats) { 356 | if (low < high) { 357 | const pi = yield* partition(arr, low, high, yieldCompare, stats); // 분할 수행 및 피벗 위치 반환 358 | yield* quickSortRecursive(arr, low, pi - 1, yieldCompare, stats); // 왼쪽 부분 배열 정렬 359 | yield* quickSortRecursive(arr, pi + 1, high, yieldCompare, stats); // 오른쪽 부분 배열 정렬 360 | } 361 | 362 | if (yieldCompare) { 363 | yield { 364 | array: [...arr], 365 | swappedIndexes: [], 366 | compareIndexes: [], 367 | comparisons: stats.comparisons, 368 | swaps: stats.swaps, 369 | }; 370 | } 371 | } 372 | 373 | // 퀵 정렬을 수행하는 메인 함수 374 | return function* quickSort(arr, yieldCompare = true) { 375 | const stats = { comparisons: 0, swaps: 0 }; 376 | yield* quickSortRecursive(arr, 0, arr.length - 1, yieldCompare, stats); 377 | return arr; 378 | }; 379 | })(); 380 | 381 | /** 382 | * 버블 정렬(Bubble Sort) 383 | * 인접한 두 요소를 비교하여 작은 값을 앞으로 이동시키는 방식으로 동작하는 정렬 알고리즘이다. 384 | * 배열이 정렬될 때까지 반복하며, 가장 큰 값이 점차 뒤로 이동한다. 385 | * 386 | * == 버블 정렬의 작동 방식 == 387 | * 1. 배열의 첫 번째 요소부터 시작하여 인접한 요소와 비교한다. 388 | * 2. 두 요소를 비교하여, 앞의 요소가 뒤의 요소보다 크면 위치를 교환한다. 389 | * 3. 배열 끝까지 비교를 반복하고, 그 과정에서 가장 큰 요소가 배열의 끝으로 이동한다. 390 | * 4. 배열이 정렬될 때까지 1~3 단계를 반복한다. 391 | */ 392 | 393 | function* bubbleSort(arr, yieldCompare = true) { 394 | const n = arr.length; 395 | let stats = { comparisons: 0, swaps: 0 }; 396 | 397 | for (let i = 0; i < n - 1; i++) { 398 | let swapped = false; 399 | 400 | for (let j = 0; j < n - 1 - i; j++) { 401 | stats.comparisons++; 402 | if (yieldCompare) { 403 | yield { 404 | array: [...arr], 405 | swappedIndexes: [], 406 | compareIndexes: [j, j + 1], 407 | comparisons: stats.comparisons, 408 | swaps: stats.swaps, 409 | }; 410 | } 411 | 412 | // 인접 요소 비교 후 교환 413 | if (arr[j] > arr[j + 1]) { 414 | [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; 415 | swapped = true; 416 | stats.swaps++; 417 | 418 | if (yieldCompare) { 419 | yield { 420 | array: [...arr], 421 | swappedIndexes: [j, j + 1], 422 | compareIndexes: [], 423 | comparisons: stats.comparisons, 424 | swaps: stats.swaps, 425 | }; 426 | } 427 | } 428 | } 429 | 430 | // 교환이 한 번도 이루어지지 않으면 정렬이 완료된 것으로 간주하고 종료 431 | if (!swapped) break; 432 | } 433 | 434 | if (yieldCompare) { 435 | yield { 436 | array: [...arr], 437 | swappedIndexes: [], 438 | compareIndexes: [], 439 | comparisons: stats.comparisons, 440 | swaps: stats.swaps, 441 | }; 442 | } 443 | 444 | return arr; 445 | } 446 | 447 | /** 448 | * 칵테일 쉐이커 정렬(Cocktail Shaker Sort) 449 | * 버블 정렬의 변형으로, 양방향으로 배열을 순회하며 정렬하는 알고리즘이다. 450 | * 한 방향으로 끝까지 이동한 후, 반대 방향으로 다시 이동하면서 요소를 정렬한다. 451 | * 452 | * == 칵테일 쉐이커 정렬의 작동 방식 == 453 | * 1. 배열의 왼쪽에서 오른쪽으로 이동하며 인접한 요소를 비교하고 교환한다. 454 | * 2. 끝까지 도달하면 방향을 반대로 바꿔 오른쪽에서 왼쪽으로 이동하며 다시 정렬한다. 455 | * 3. 배열이 정렬될 때까지 1~2 단계를 반복한다. 456 | */ 457 | 458 | function* cocktailShakerSort(arr, yieldCompare = true) { 459 | let start = 0; 460 | let end = arr.length - 1; 461 | let stats = { comparisons: 0, swaps: 0 }; 462 | 463 | while (start < end) { 464 | let swapped = false; 465 | 466 | // 왼쪽에서 오른쪽으로 이동하며 정렬 467 | for (let i = start; i < end; i++) { 468 | stats.comparisons++; 469 | if (yieldCompare) { 470 | yield { 471 | array: [...arr], 472 | swappedIndexes: [], 473 | compareIndexes: [i, i + 1], 474 | comparisons: stats.comparisons, 475 | swaps: stats.swaps, 476 | }; 477 | } 478 | 479 | if (arr[i] > arr[i + 1]) { 480 | [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]]; 481 | stats.swaps++; 482 | swapped = true; 483 | 484 | if (yieldCompare) { 485 | yield { 486 | array: [...arr], 487 | swappedIndexes: [i, i + 1], 488 | compareIndexes: [], 489 | comparisons: stats.comparisons, 490 | swaps: stats.swaps, 491 | }; 492 | } 493 | } 494 | } 495 | 496 | // 더 이상 교환이 없으면 정렬이 완료된 것으로 간주하고 종료 497 | if (!swapped) break; 498 | 499 | // 오른쪽 끝을 하나 줄임 (이미 정렬된 부분) 500 | end--; 501 | 502 | swapped = false; 503 | 504 | // 오른쪽에서 왼쪽으로 이동하며 정렬 505 | for (let i = end; i > start; i--) { 506 | stats.comparisons++; 507 | if (yieldCompare) { 508 | yield { 509 | array: [...arr], 510 | swappedIndexes: [], 511 | compareIndexes: [i - 1, i], 512 | comparisons: stats.comparisons, 513 | swaps: stats.swaps, 514 | }; 515 | } 516 | 517 | if (arr[i - 1] > arr[i]) { 518 | [arr[i - 1], arr[i]] = [arr[i], arr[i - 1]]; 519 | stats.swaps++; 520 | swapped = true; 521 | 522 | if (yieldCompare) { 523 | yield { 524 | array: [...arr], 525 | swappedIndexes: [i - 1, i], 526 | compareIndexes: [], 527 | comparisons: stats.comparisons, 528 | swaps: stats.swaps, 529 | }; 530 | } 531 | } 532 | } 533 | 534 | // 더 이상 교환이 없으면 정렬이 완료된 것으로 간주하고 종료 535 | if (!swapped) break; 536 | 537 | // 왼쪽 끝을 하나 늘림 (이미 정렬된 부분) 538 | start++; 539 | } 540 | 541 | if (yieldCompare) { 542 | yield { 543 | array: [...arr], 544 | swappedIndexes: [], 545 | compareIndexes: [], 546 | comparisons: stats.comparisons, 547 | swaps: stats.swaps, 548 | }; 549 | } 550 | 551 | return arr; 552 | } 553 | 554 | /** 555 | * 놈 정렬(Gnome Sort) 556 | * 단순한 정렬 알고리즘으로, 삽입 정렬과 유사하게 동작하지만 인덱스를 양방향으로 이동하면서 정렬한다. 557 | * 정렬이 끝날 때까지 앞뒤로 이동하며 정렬이 되지 않은 부분을 정렬한다. 558 | * 559 | * == 놈 정렬의 작동 방식 == 560 | * 1. 배열의 첫 번째 요소부터 시작하여 인접한 요소를 비교한다. 561 | * 2. 현재 요소가 이전 요소보다 작으면 두 요소의 위치를 교환하고, 인덱스를 감소시켜 다시 이전 요소와 비교한다. 562 | * 3. 그렇지 않으면 인덱스를 증가시켜 다음 요소로 이동한다. 563 | * 4. 배열의 끝에 도달할 때까지 2~3 단계를 반복한다. 564 | */ 565 | 566 | function* gnomeSort(arr, yieldCompare = true) { 567 | let index = 0; 568 | let stats = { comparisons: 0, swaps: 0 }; 569 | 570 | while (index < arr.length) { 571 | if (index === 0) { 572 | index++; 573 | } 574 | 575 | stats.comparisons++; 576 | if (yieldCompare) { 577 | yield { 578 | array: [...arr], 579 | swappedIndexes: [], 580 | compareIndexes: [index - 1, index], 581 | comparisons: stats.comparisons, 582 | swaps: stats.swaps, 583 | }; 584 | } 585 | 586 | if (arr[index] >= arr[index - 1]) { 587 | index++; 588 | } else { 589 | [arr[index], arr[index - 1]] = [arr[index - 1], arr[index]]; 590 | stats.swaps++; 591 | 592 | if (yieldCompare) { 593 | yield { 594 | array: [...arr], 595 | swappedIndexes: [index - 1, index], 596 | compareIndexes: [], 597 | comparisons: stats.comparisons, 598 | swaps: stats.swaps, 599 | }; 600 | } 601 | index--; 602 | } 603 | } 604 | 605 | if (yieldCompare) { 606 | yield { 607 | array: [...arr], 608 | swappedIndexes: [], 609 | compareIndexes: [], 610 | comparisons: stats.comparisons, 611 | swaps: stats.swaps, 612 | }; 613 | } 614 | 615 | return arr; 616 | } 617 | 618 | /** 619 | * 콤 정렬(Comb Sort) 620 | * 버블 정렬의 개선된 버전으로, 큰 간격(gap)으로 요소를 비교하고 정렬하며, 621 | * 반복할 때마다 간격을 줄여 나가면서 정렬하는 방식으로 동작한다. 622 | * 마지막 단계에서 간격이 1이 되면 버블 정렬과 동일한 방식으로 동작한다. 623 | * 624 | * == 콤 정렬의 작동 방식 == 625 | * 1. 초기 간격(gap)을 배열 길이로 설정한다. 626 | * 2. 간격을 점차 줄여가며 해당 간격으로 요소를 비교하고 교환한다. 627 | * 3. 간격이 1이 될 때까지 2번 단계를 반복한다. 628 | * 4. 간격이 1이 되면, 버블 정렬과 유사하게 동작하여 배열을 완전히 정렬한다. 629 | */ 630 | 631 | function* combSort(arr, yieldCompare = true) { 632 | const shrinkFactor = 1.3; // 간격을 줄이는 비율 633 | let gap = arr.length; // 초기 간격 634 | let sorted = false; // 정렬 여부 플래그 635 | let stats = { comparisons: 0, swaps: 0 }; 636 | 637 | while (!sorted) { 638 | // 간격을 줄임 639 | gap = Math.floor(gap / shrinkFactor); 640 | if (gap <= 1) { 641 | gap = 1; 642 | sorted = true; // 간격이 1일 때, 마지막 패스임을 나타냄 643 | } 644 | 645 | let i = 0; 646 | while (i + gap < arr.length) { 647 | stats.comparisons++; 648 | if (yieldCompare) { 649 | yield { 650 | array: [...arr], 651 | swappedIndexes: [], 652 | compareIndexes: [i, i + gap], 653 | comparisons: stats.comparisons, 654 | swaps: stats.swaps, 655 | }; 656 | } 657 | 658 | // 요소를 비교하고 필요 시 교환 659 | if (arr[i] > arr[i + gap]) { 660 | [arr[i], arr[i + gap]] = [arr[i + gap], arr[i]]; 661 | stats.swaps++; 662 | sorted = false; // 교환이 발생하면 정렬되지 않았음을 나타냄 663 | 664 | if (yieldCompare) { 665 | yield { 666 | array: [...arr], 667 | swappedIndexes: [i, i + gap], 668 | compareIndexes: [], 669 | comparisons: stats.comparisons, 670 | swaps: stats.swaps, 671 | }; 672 | } 673 | } 674 | 675 | i++; 676 | } 677 | } 678 | 679 | if (yieldCompare) { 680 | yield { 681 | array: [...arr], 682 | swappedIndexes: [], 683 | compareIndexes: [], 684 | comparisons: stats.comparisons, 685 | swaps: stats.swaps, 686 | }; 687 | } 688 | 689 | return arr; 690 | } 691 | 692 | /** 693 | * 셸 정렬(Shell Sort) 694 | * 삽입 정렬의 일반화된 버전으로, 간격(gap)을 두고 떨어진 요소들끼리 삽입 정렬을 수행한 후, 695 | * 점차 간격을 줄여가면서 전체 배열을 정렬하는 알고리즘이다. 696 | * 간격이 1이 되면 일반적인 삽입 정렬과 동일한 방식으로 동작한다. 697 | * 698 | * == 셸 정렬의 작동 방식 == 699 | * 1. 초기 간격(gap)을 설정한다. 일반적으로 배열 길이의 절반으로 시작한다. 700 | * 2. 해당 간격으로 떨어진 요소들을 삽입 정렬한다. 701 | * 3. 간격을 줄이고 2번 단계를 반복한다. 702 | * 4. 간격이 1이 되면, 마지막으로 전체 배열을 삽입 정렬한다. 703 | */ 704 | 705 | function* shellSort(arr, yieldCompare = true) { 706 | let n = arr.length; 707 | let gap = Math.floor(n / 2); 708 | let stats = { comparisons: 0, swaps: 0 }; 709 | 710 | while (gap > 0) { 711 | for (let i = gap; i < n; i++) { 712 | let temp = arr[i]; 713 | let j = i; 714 | 715 | // 간격이 떨어진 요소들끼리 삽입 정렬 716 | while (j >= gap && arr[j - gap] > temp) { 717 | stats.comparisons++; 718 | if (yieldCompare) { 719 | yield { 720 | array: [...arr], 721 | swappedIndexes: [], 722 | compareIndexes: [j, j - gap], 723 | comparisons: stats.comparisons, 724 | swaps: stats.swaps, 725 | }; 726 | } 727 | 728 | arr[j] = arr[j - gap]; 729 | stats.swaps++; 730 | if (yieldCompare) { 731 | yield { 732 | array: [...arr], 733 | swappedIndexes: [j, j - gap], 734 | compareIndexes: [], 735 | comparisons: stats.comparisons, 736 | swaps: stats.swaps, 737 | }; 738 | } 739 | 740 | j -= gap; 741 | } 742 | 743 | arr[j] = temp; 744 | 745 | if (yieldCompare && j !== i) { 746 | yield { 747 | array: [...arr], 748 | swappedIndexes: [j], 749 | compareIndexes: [], 750 | comparisons: stats.comparisons, 751 | swaps: stats.swaps, 752 | }; 753 | } 754 | } 755 | 756 | gap = Math.floor(gap / 2); // 간격을 줄임 757 | } 758 | 759 | if (yieldCompare) { 760 | yield { 761 | array: [...arr], 762 | swappedIndexes: [], 763 | compareIndexes: [], 764 | comparisons: stats.comparisons, 765 | swaps: stats.swaps, 766 | }; 767 | } 768 | 769 | return arr; 770 | } 771 | /** 772 | * 힙 정렬(Heap Sort) 773 | * 이진 힙(Binary Heap)을 이용한 정렬 알고리즘으로, 최대 힙을 구성하여 가장 큰 값을 배열의 끝으로 보내고, 774 | * 남은 부분을 다시 힙으로 구성해 정렬한다. O(n log n)의 시간 복잡도를 가지며, 안정 정렬은 아니다. 775 | * 776 | * == 힙 정렬의 작동 방식 == 777 | * 1. 주어진 배열을 최대 힙으로 구성한다. 778 | * 2. 힙의 루트(최대값)를 배열의 끝으로 이동시킨다. 779 | * 3. 힙 크기를 줄이고, 남은 부분을 다시 최대 힙으로 재구성한다. 780 | * 4. 2~3 단계를 반복하여 배열이 정렬될 때까지 수행한다. 781 | */ 782 | 783 | function* heapSort(arr, yieldCompare = true) { 784 | let n = arr.length; 785 | let stats = { comparisons: 0, swaps: 0 }; 786 | 787 | // 최대 힙을 구성하는 함수 788 | function* heapify(arr, n, i) { 789 | let largest = i; // 루트 790 | let left = 2 * i + 1; // 왼쪽 자식 791 | let right = 2 * i + 2; // 오른쪽 자식 792 | 793 | // 왼쪽 자식이 루트보다 크다면 794 | if (left < n && arr[left] > arr[largest]) { 795 | largest = left; 796 | } 797 | 798 | // 오른쪽 자식이 현재 가장 큰 값보다 크다면 799 | if (right < n && arr[right] > arr[largest]) { 800 | largest = right; 801 | } 802 | 803 | // 가장 큰 값이 루트가 아니라면 804 | if (largest !== i) { 805 | stats.comparisons++; 806 | if (yieldCompare) { 807 | yield { 808 | array: [...arr], 809 | swappedIndexes: [], 810 | compareIndexes: [i, largest], 811 | comparisons: stats.comparisons, 812 | swaps: stats.swaps, 813 | }; 814 | } 815 | 816 | [arr[i], arr[largest]] = [arr[largest], arr[i]]; 817 | stats.swaps++; 818 | 819 | if (yieldCompare) { 820 | yield { 821 | array: [...arr], 822 | swappedIndexes: [i, largest], 823 | compareIndexes: [], 824 | comparisons: stats.comparisons, 825 | swaps: stats.swaps, 826 | }; 827 | } 828 | 829 | // 재귀적으로 힙을 재구성 830 | yield* heapify(arr, n, largest); 831 | } 832 | } 833 | 834 | // 초기 배열을 최대 힙으로 변환 835 | for (let i = Math.floor(n / 2) - 1; i >= 0; i--) { 836 | yield* heapify(arr, n, i); 837 | } 838 | 839 | // 하나씩 요소를 힙에서 추출하여 정렬 840 | for (let i = n - 1; i > 0; i--) { 841 | [arr[0], arr[i]] = [arr[i], arr[0]]; 842 | stats.swaps++; 843 | 844 | if (yieldCompare) { 845 | yield { 846 | array: [...arr], 847 | swappedIndexes: [0, i], 848 | compareIndexes: [], 849 | comparisons: stats.comparisons, 850 | swaps: stats.swaps, 851 | }; 852 | } 853 | 854 | yield* heapify(arr, i, 0); 855 | } 856 | 857 | if (yieldCompare) { 858 | yield { 859 | array: [...arr], 860 | swappedIndexes: [], 861 | compareIndexes: [], 862 | comparisons: stats.comparisons, 863 | swaps: stats.swaps, 864 | }; 865 | } 866 | 867 | return arr; 868 | } 869 | 870 | /** 871 | * 홀짝 정렬(Odd-Even Sort) 872 | * 873 | * 홀짝 정렬은 버블 정렬의 변형으로, 인접한 두 요소를 비교하여 정렬하지만 874 | * 번갈아 가며 홀수 인덱스 쌍과 짝수 인덱스 쌍을 비교하는 방식으로 동작한다. 875 | * 안정 정렬이며, 최악의 경우 시간 복잡도는 O(n^2)이다. 876 | * 877 | * == 홀짝 정렬의 작동 방식 == 878 | * 1. 배열의 인접한 요소들을 번갈아 가며 홀수 인덱스 쌍과 짝수 인덱스 쌍으로 비교하고 교환한다. 879 | * 2. 배열이 완전히 정렬될 때까지 1번 단계를 반복한다. 880 | */ 881 | 882 | function* oddEvenSort(arr, yieldCompare = true) { 883 | let n = arr.length; 884 | let sorted = false; 885 | let stats = { comparisons: 0, swaps: 0 }; 886 | 887 | while (!sorted) { 888 | sorted = true; 889 | 890 | // 홀수 인덱스 쌍 비교 및 정렬 891 | for (let i = 1; i < n - 1; i += 2) { 892 | stats.comparisons++; 893 | if (yieldCompare) { 894 | yield { 895 | array: [...arr], 896 | swappedIndexes: [], 897 | compareIndexes: [i, i + 1], 898 | comparisons: stats.comparisons, 899 | swaps: stats.swaps, 900 | }; 901 | } 902 | 903 | if (arr[i] > arr[i + 1]) { 904 | [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]]; 905 | stats.swaps++; 906 | sorted = false; 907 | 908 | if (yieldCompare) { 909 | yield { 910 | array: [...arr], 911 | swappedIndexes: [i, i + 1], 912 | compareIndexes: [], 913 | comparisons: stats.comparisons, 914 | swaps: stats.swaps, 915 | }; 916 | } 917 | } 918 | } 919 | 920 | // 짝수 인덱스 쌍 비교 및 정렬 921 | for (let i = 0; i < n - 1; i += 2) { 922 | stats.comparisons++; 923 | if (yieldCompare) { 924 | yield { 925 | array: [...arr], 926 | swappedIndexes: [], 927 | compareIndexes: [i, i + 1], 928 | comparisons: stats.comparisons, 929 | swaps: stats.swaps, 930 | }; 931 | } 932 | 933 | if (arr[i] > arr[i + 1]) { 934 | [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]]; 935 | stats.swaps++; 936 | sorted = false; 937 | 938 | if (yieldCompare) { 939 | yield { 940 | array: [...arr], 941 | swappedIndexes: [i, i + 1], 942 | compareIndexes: [], 943 | comparisons: stats.comparisons, 944 | swaps: stats.swaps, 945 | }; 946 | } 947 | } 948 | } 949 | } 950 | 951 | if (yieldCompare) { 952 | yield { 953 | array: [...arr], 954 | swappedIndexes: [], 955 | compareIndexes: [], 956 | comparisons: stats.comparisons, 957 | swaps: stats.swaps, 958 | }; 959 | } 960 | 961 | return arr; 962 | } 963 | 964 | /** 965 | * 바이토닉 정렬(Bitonic Sort) 966 | * 967 | * 바이토닉 정렬은 병렬 처리를 위해 설계된 비교 정렬 알고리즘으로, 968 | * O(log^2 n)의 시간 복잡도를 가진다. 이 알고리즘은 입력 배열을 969 | * 바이토닉 시퀀스로 정렬한 후, 그 시퀀스를 병합하여 전체를 정렬한다. 970 | * 971 | * == 바이토닉 정렬의 작동 방식 == 972 | * 1. 입력 배열을 점차적으로 증가하는 부분과 감소하는 부분으로 나눈다. 973 | * 2. 각각의 부분을 재귀적으로 정렬한다. 974 | * 3. 정렬된 두 부분을 병합하여 하나의 정렬된 배열을 만든다. 975 | */ 976 | function* bitonicSort(arr, up = true, yieldCompare = true) { 977 | let n = arr.length; 978 | let stats = { comparisons: 0, swaps: 0 }; 979 | 980 | // 두 부분을 병합하는 함수 981 | function* bitonicMerge(arr, low, cnt, up) { 982 | if (cnt <= 1) return; 983 | 984 | let mid = Math.floor(cnt / 2); 985 | for (let i = low; i < low + mid; i++) { 986 | stats.comparisons++; 987 | if (yieldCompare) { 988 | yield { 989 | array: [...arr], 990 | swappedIndexes: [], 991 | compareIndexes: [i, i + mid], 992 | comparisons: stats.comparisons, 993 | swaps: stats.swaps, 994 | }; 995 | } 996 | 997 | if ((arr[i] > arr[i + mid]) === up) { 998 | [arr[i], arr[i + mid]] = [arr[i + mid], arr[i]]; 999 | stats.swaps++; 1000 | 1001 | if (yieldCompare) { 1002 | yield { 1003 | array: [...arr], 1004 | swappedIndexes: [i, i + mid], 1005 | compareIndexes: [], 1006 | comparisons: stats.comparisons, 1007 | swaps: stats.swaps, 1008 | }; 1009 | } 1010 | } 1011 | } 1012 | 1013 | yield* bitonicMerge(arr, low, mid, up); 1014 | yield* bitonicMerge(arr, low + mid, mid, up); 1015 | } 1016 | 1017 | // 바이토닉 정렬 수행 1018 | function* bitonicSortRec(arr, low, cnt, up) { 1019 | if (cnt <= 1) return; 1020 | 1021 | let mid = Math.floor(cnt / 2); 1022 | 1023 | yield* bitonicSortRec(arr, low, mid, true); 1024 | yield* bitonicSortRec(arr, low + mid, mid, false); 1025 | 1026 | yield* bitonicMerge(arr, low, cnt, up); 1027 | } 1028 | 1029 | yield* bitonicSortRec(arr, 0, n, up); 1030 | 1031 | if (yieldCompare) { 1032 | yield { 1033 | array: [...arr], 1034 | comparisons: stats.comparisons, 1035 | swaps: stats.swaps, 1036 | }; 1037 | } 1038 | 1039 | return arr; 1040 | } 1041 | 1042 | /** 1043 | * 사이클 정렬(Cycle Sort) 1044 | * 1045 | * 사이클 정렬은 배열 내에서 불필요한 복사 없이 최소 스왑 횟수로 정렬하는 1046 | * 비교 기반 정렬 알고리즘이다. 사이클 정렬의 시간 복잡도는 O(n^2)이지만, 1047 | * 각 요소를 정확히 한 번씩만 위치에 놓으므로 특정 상황에서 효율적이다. 1048 | * 1049 | * == 사이클 정렬의 작동 방식 == 1050 | * 1. 배열의 각 요소를 한 번씩 사이클로 위치를 찾아 이동시킨다. 1051 | * 2. 각 사이클을 정리하면서 요소를 제자리에 위치시킨다. 1052 | * 3. 필요하면 중복 요소를 건너뛰고 진행한다. 1053 | */ 1054 | function* cycleSort(arr, yieldCompare = true) { 1055 | // 배열의 길이를 저장한다. 1056 | let n = arr.length; 1057 | // 비교 및 스왑 횟수를 추적하는 객체를 선언한다. 1058 | let stats = { comparisons: 0, swaps: 0 }; 1059 | 1060 | // 배열의 각 요소에 대해 사이클을 시작한다. 1061 | for (let cycleStart = 0; cycleStart < n - 1; cycleStart++) { 1062 | // 현재 요소를 저장한다. 1063 | let item = arr[cycleStart]; 1064 | // 현재 요소의 올바른 위치를 찾기 위해 위치를 추적한다. 1065 | let pos = cycleStart; 1066 | 1067 | // 현재 요소보다 작은 요소의 개수를 센다. 1068 | for (let i = cycleStart + 1; i < n; i++) { 1069 | stats.comparisons++; 1070 | if (arr[i] < item) { 1071 | pos++; 1072 | } 1073 | 1074 | // 배열의 상태를 외부로 전달하기 위해 yield한다. 1075 | if (yieldCompare) { 1076 | yield { 1077 | array: [...arr], 1078 | swappedIndexes: [], 1079 | compareIndexes: [i], 1080 | comparisons: stats.comparisons, 1081 | swaps: stats.swaps, 1082 | }; 1083 | } 1084 | } 1085 | 1086 | // 요소가 이미 제자리에 있다면 다음 사이클로 넘어간다. 1087 | if (pos === cycleStart) { 1088 | continue; 1089 | } 1090 | 1091 | // 동일한 요소가 있는 경우 위치를 건너뛰고 조정한다. 1092 | while (item === arr[pos]) { 1093 | pos++; 1094 | } 1095 | 1096 | // 요소를 제자리에 놓고 스왑 횟수를 기록한다. 1097 | if (pos !== cycleStart) { 1098 | [arr[pos], item] = [item, arr[pos]]; 1099 | stats.swaps++; 1100 | 1101 | if (yieldCompare) { 1102 | yield { 1103 | array: [...arr], 1104 | swappedIndexes: [pos], 1105 | compareIndexes: [], 1106 | comparisons: stats.comparisons, 1107 | swaps: stats.swaps, 1108 | }; 1109 | } 1110 | } 1111 | 1112 | // 남은 사이클을 계속 수행한다. 1113 | while (pos !== cycleStart) { 1114 | pos = cycleStart; 1115 | 1116 | // 현재 요소보다 작은 요소의 개수를 다시 센다. 1117 | for (let i = cycleStart + 1; i < n; i++) { 1118 | stats.comparisons++; 1119 | if (arr[i] < item) { 1120 | pos++; 1121 | } 1122 | 1123 | // 배열의 상태를 외부로 전달하기 위해 yield한다. 1124 | if (yieldCompare) { 1125 | yield { 1126 | array: [...arr], 1127 | swappedIndexes: [], 1128 | compareIndexes: [i], 1129 | comparisons: stats.comparisons, 1130 | swaps: stats.swaps, 1131 | }; 1132 | } 1133 | } 1134 | 1135 | // 동일한 요소가 있는 경우 위치를 건너뛰고 조정한다. 1136 | while (item === arr[pos]) { 1137 | pos++; 1138 | } 1139 | 1140 | // 요소를 제자리에 놓고 스왑 횟수를 기록한다. 1141 | if (item !== arr[pos]) { 1142 | [arr[pos], item] = [item, arr[pos]]; 1143 | stats.swaps++; 1144 | 1145 | if (yieldCompare) { 1146 | yield { 1147 | array: [...arr], 1148 | swappedIndexes: [pos], 1149 | compareIndexes: [], 1150 | comparisons: stats.comparisons, 1151 | swaps: stats.swaps, 1152 | }; 1153 | } 1154 | } 1155 | } 1156 | } 1157 | 1158 | // 최종적으로 정렬된 배열을 반환한다. 1159 | if (yieldCompare) { 1160 | yield { 1161 | array: [...arr], 1162 | comparisons: stats.comparisons, 1163 | swaps: stats.swaps, 1164 | }; 1165 | } 1166 | 1167 | return arr; 1168 | } 1169 | 1170 | /** 1171 | * LSD 기수 정렬(LSD Radix Sort) 1172 | * 1173 | * LSD 기수 정렬은 비교 기반이 아닌 정렬 알고리즘으로, 정수 또는 문자열을 1174 | * 자리수(또는 문자)의 최하위부터 시작하여 정렬한다. 이 알고리즘은 안정적이며 1175 | * O(nk)의 시간 복잡도를 가지며, 여기서 n은 숫자의 개수, k는 숫자의 최대 자릿수이다. 1176 | * 1177 | * == LSD 기수 정렬의 작동 방식 == 1178 | * 1. 배열의 각 숫자를 자릿수별로 분류한다. 1179 | * 2. 각 자릿수마다 안정적인 계수 정렬(Counting Sort)을 사용해 정렬한다. 1180 | * 3. 자릿수 순서대로 정렬된 배열을 얻을 때까지 반복한다. 1181 | */ 1182 | function* lsdRadixSort(arr, yieldCompare = true) { 1183 | // 최대 자릿수를 찾기 위해 배열의 최대값을 확인한다. 1184 | const maxNum = Math.max(...arr); 1185 | // 최대 자릿수를 기반으로 반복 횟수를 결정한다. 1186 | let exp = 1; 1187 | let stats = { comparisons: 0, swaps: 0 }; 1188 | 1189 | // 자릿수를 기반으로 반복하여 정렬한다. 1190 | while (Math.floor(maxNum / exp) > 0) { 1191 | // 계수 정렬을 위한 버킷을 초기화한다 (0~9). 1192 | let output = new Array(arr.length).fill(0); 1193 | let count = new Array(10).fill(0); 1194 | 1195 | // 각 자릿수에 따라 요소를 분류한다. 1196 | for (let i = 0; i < arr.length; i++) { 1197 | let digit = Math.floor(arr[i] / exp) % 10; 1198 | yield { 1199 | array: [...arr], 1200 | swappedIndexes: [], 1201 | compareIndexes: [i], 1202 | comparisons: stats.comparisons, 1203 | swaps: stats.swaps, 1204 | }; 1205 | count[digit]++; 1206 | } 1207 | 1208 | // 누적 카운트를 계산하여 위치를 결정한다. 1209 | for (let i = 1; i < 10; i++) { 1210 | count[i] += count[i - 1]; 1211 | yield { 1212 | array: [...arr], 1213 | swappedIndexes: [], 1214 | compareIndexes: [], 1215 | comparisons: stats.comparisons, 1216 | swaps: stats.swaps, 1217 | }; 1218 | } 1219 | 1220 | // 배열의 요소들을 자릿수에 따라 정렬된 위치에 배치한다. 1221 | for (let i = arr.length - 1; i >= 0; i--) { 1222 | let digit = Math.floor(arr[i] / exp) % 10; 1223 | output[--count[digit]] = arr[i]; 1224 | yield { 1225 | array: [...arr], 1226 | swappedIndexes: [], 1227 | compareIndexes: [i], 1228 | comparisons: stats.comparisons, 1229 | swaps: stats.swaps, 1230 | }; 1231 | } 1232 | 1233 | // 정렬된 결과를 원래 배열에 복사한다. 1234 | for (let i = 0; i < arr.length; i++) { 1235 | arr[i] = output[i]; 1236 | stats.swaps++; 1237 | 1238 | // 배열의 상태를 외부로 전달하기 위해 yield한다. 1239 | if (yieldCompare) { 1240 | yield { 1241 | array: [...arr], 1242 | swappedIndexes: [i], 1243 | compareIndexes: [], 1244 | comparisons: stats.comparisons, 1245 | swaps: stats.swaps, 1246 | }; 1247 | } 1248 | } 1249 | 1250 | // 다음 자릿수로 이동한다. 1251 | exp *= 10; 1252 | } 1253 | 1254 | // 최종적으로 정렬된 배열을 반환한다. 1255 | if (yieldCompare) { 1256 | yield { 1257 | array: [...arr], 1258 | comparisons: stats.comparisons, 1259 | swaps: stats.swaps, 1260 | }; 1261 | } 1262 | 1263 | return arr; 1264 | } 1265 | 1266 | /** 1267 | * 보고 정렬(Bogo Sort) 1268 | * 1269 | * 보고 정렬은 무작위로 요소를 섞어가며 정렬이 완료될 때까지 반복하는 알고리즘이다. 1270 | * 이 알고리즘은 최악의 경우 무한히 실행될 수 있으며, 평균 시간 복잡도는 O(n!)이다. 1271 | * 이는 실용적이지 않은 알고리즘이지만, 개념적으로 이해하기 쉽다는 특징이 있다. 1272 | * 1273 | * == 보고 정렬의 작동 방식 == 1274 | * 1. 배열이 정렬되었는지 확인한다. 1275 | * 2. 정렬되지 않았다면, 배열의 요소를 무작위로 섞는다. 1276 | * 3. 배열이 정렬될 때까지 1-2 단계를 반복한다. 1277 | */ 1278 | function* bogoSort(arr, yieldCompare = true) { 1279 | let comparisons = 0; 1280 | let swaps = 0; 1281 | 1282 | // 배열이 정렬되었는지 확인하는 함수 1283 | function isSorted(arr) { 1284 | for (let i = 1; i < arr.length; i++) { 1285 | comparisons++; 1286 | if (arr[i - 1] > arr[i]) { 1287 | return false; 1288 | } 1289 | } 1290 | return true; 1291 | } 1292 | 1293 | 1294 | // 정렬될 때까지 반복 1295 | while (!isSorted(arr)) { 1296 | // 배열을 무작위로 섞는다 1297 | for (let i = arr.length - 1; i > 0; i--) { 1298 | const j = Math.floor(Math.random() * (i + 1)); 1299 | [arr[i], arr[j]] = [arr[j], arr[i]]; 1300 | swaps++; 1301 | yield { 1302 | array: [...arr], 1303 | swappedIndexes: [i, j], 1304 | compareIndexes: [], 1305 | comparisons: comparisons, 1306 | swaps: swaps, 1307 | }; 1308 | } 1309 | 1310 | } 1311 | 1312 | // 최종적으로 정렬된 배열을 반환한다 1313 | if (yieldCompare) { 1314 | yield { 1315 | array: [...arr], 1316 | swappedIndexes: [], 1317 | compareIndexes: [], 1318 | comparisons: comparisons, 1319 | swaps: swaps, 1320 | }; 1321 | } 1322 | 1323 | return arr; 1324 | } --------------------------------------------------------------------------------