diff --git a/assets/avatar/avatar_yuyanhuang.jpg b/assets/avatar/avatar_yuyanhuang.jpg new file mode 100644 index 000000000..55bbb528a Binary files /dev/null and b/assets/avatar/avatar_yuyanhuang.jpg differ diff --git a/assets/javascripts/bundle.c2b142ea.min.js b/assets/javascripts/bundle.c2b142ea.min.js index cb5773b85..02158b76a 100644 --- a/assets/javascripts/bundle.c2b142ea.min.js +++ b/assets/javascripts/bundle.c2b142ea.min.js @@ -1,4 +1,4 @@ "use strict";(()=>{var xc=Object.create;var kn=Object.defineProperty,wc=Object.defineProperties,Ec=Object.getOwnPropertyDescriptor,Tc=Object.getOwnPropertyDescriptors,Sc=Object.getOwnPropertyNames,Dr=Object.getOwnPropertySymbols,Oc=Object.getPrototypeOf,An=Object.prototype.hasOwnProperty,Fo=Object.prototype.propertyIsEnumerable;var jo=(e,t,r)=>t in e?kn(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,H=(e,t)=>{for(var r in t||(t={}))An.call(t,r)&&jo(e,r,t[r]);if(Dr)for(var r of Dr(t))Fo.call(t,r)&&jo(e,r,t[r]);return e},He=(e,t)=>wc(e,Tc(t));var gr=(e,t)=>{var r={};for(var n in e)An.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&Dr)for(var n of Dr(e))t.indexOf(n)<0&&Fo.call(e,n)&&(r[n]=e[n]);return r};var Cn=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Lc=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Sc(t))!An.call(e,o)&&o!==r&&kn(e,o,{get:()=>t[o],enumerable:!(n=Ec(t,o))||n.enumerable});return e};var _r=(e,t,r)=>(r=e!=null?xc(Oc(e)):{},Lc(t||!e||!e.__esModule?kn(r,"default",{value:e,enumerable:!0}):r,e));var Uo=(e,t,r)=>new Promise((n,o)=>{var i=c=>{try{s(r.next(c))}catch(l){o(l)}},a=c=>{try{s(r.throw(c))}catch(l){o(l)}},s=c=>c.done?n(c.value):Promise.resolve(c.value).then(i,a);s((r=r.apply(e,t)).next())});var Do=Cn((Hn,No)=>{(function(e,t){typeof Hn=="object"&&typeof No!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(Hn,(function(){"use strict";function e(r){var n=!0,o=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(_){return!!(_&&_!==document&&_.nodeName!=="HTML"&&_.nodeName!=="BODY"&&"classList"in _&&"contains"in _.classList)}function c(_){var de=_.type,be=_.tagName;return!!(be==="INPUT"&&a[de]&&!_.readOnly||be==="TEXTAREA"&&!_.readOnly||_.isContentEditable)}function l(_){_.classList.contains("focus-visible")||(_.classList.add("focus-visible"),_.setAttribute("data-focus-visible-added",""))}function u(_){_.hasAttribute("data-focus-visible-added")&&(_.classList.remove("focus-visible"),_.removeAttribute("data-focus-visible-added"))}function p(_){_.metaKey||_.altKey||_.ctrlKey||(s(r.activeElement)&&l(r.activeElement),n=!0)}function d(_){n=!1}function m(_){s(_.target)&&(n||c(_.target))&&l(_.target)}function h(_){s(_.target)&&(_.target.classList.contains("focus-visible")||_.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),u(_.target))}function v(_){document.visibilityState==="hidden"&&(o&&(n=!0),x())}function x(){document.addEventListener("mousemove",E),document.addEventListener("mousedown",E),document.addEventListener("mouseup",E),document.addEventListener("pointermove",E),document.addEventListener("pointerdown",E),document.addEventListener("pointerup",E),document.addEventListener("touchmove",E),document.addEventListener("touchstart",E),document.addEventListener("touchend",E)}function w(){document.removeEventListener("mousemove",E),document.removeEventListener("mousedown",E),document.removeEventListener("mouseup",E),document.removeEventListener("pointermove",E),document.removeEventListener("pointerdown",E),document.removeEventListener("pointerup",E),document.removeEventListener("touchmove",E),document.removeEventListener("touchstart",E),document.removeEventListener("touchend",E)}function E(_){_.target.nodeName&&_.target.nodeName.toLowerCase()==="html"||(n=!1,w())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",d,!0),document.addEventListener("pointerdown",d,!0),document.addEventListener("touchstart",d,!0),document.addEventListener("visibilitychange",v,!0),x(),r.addEventListener("focus",m,!0),r.addEventListener("blur",h,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)}))});var So=Cn((M0,vs)=>{"use strict";var Gu=/["'&<>]/;vs.exports=Ju;function Ju(e){var t=""+e,r=Gu.exec(t);if(!r)return t;var n,o="",i=0,a=0;for(i=r.index;i{(function(t,r){typeof jr=="object"&&typeof Lo=="object"?Lo.exports=r():typeof define=="function"&&define.amd?define([],r):typeof jr=="object"?jr.ClipboardJS=r():t.ClipboardJS=r()})(jr,function(){return(function(){var e={686:(function(n,o,i){"use strict";i.d(o,{default:function(){return vr}});var a=i(279),s=i.n(a),c=i(370),l=i.n(c),u=i(817),p=i.n(u);function d(B){try{return document.execCommand(B)}catch(C){return!1}}var m=function(C){var k=p()(C);return d("cut"),k},h=m;function v(B){var C=document.documentElement.getAttribute("dir")==="rtl",k=document.createElement("textarea");k.style.fontSize="12pt",k.style.border="0",k.style.padding="0",k.style.margin="0",k.style.position="absolute",k.style[C?"right":"left"]="-9999px";var D=window.pageYOffset||document.documentElement.scrollTop;return k.style.top="".concat(D,"px"),k.setAttribute("readonly",""),k.value=B,k}var x=function(C,k){var D=v(C);k.container.appendChild(D);var W=p()(D);return d("copy"),D.remove(),W},w=function(C){var k=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},D="";return typeof C=="string"?D=x(C,k):C instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(C==null?void 0:C.type)?D=x(C.value,k):(D=p()(C),d("copy")),D},E=w;function _(B){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?_=function(k){return typeof k}:_=function(k){return k&&typeof Symbol=="function"&&k.constructor===Symbol&&k!==Symbol.prototype?"symbol":typeof k},_(B)}var de=function(){var C=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},k=C.action,D=k===void 0?"copy":k,W=C.container,Z=C.target,We=C.text;if(D!=="copy"&&D!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(Z!==void 0)if(Z&&_(Z)==="object"&&Z.nodeType===1){if(D==="copy"&&Z.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(D==="cut"&&(Z.hasAttribute("readonly")||Z.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(We)return E(We,{container:W});if(Z)return D==="cut"?h(Z):E(Z,{container:W})},be=de;function M(B){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?M=function(k){return typeof k}:M=function(k){return k&&typeof Symbol=="function"&&k.constructor===Symbol&&k!==Symbol.prototype?"symbol":typeof k},M(B)}function O(B,C){if(!(B instanceof C))throw new TypeError("Cannot call a class as a function")}function N(B,C){for(var k=0;k0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof W.action=="function"?W.action:this.defaultAction,this.target=typeof W.target=="function"?W.target:this.defaultTarget,this.text=typeof W.text=="function"?W.text:this.defaultText,this.container=M(W.container)==="object"?W.container:document.body}},{key:"listenClick",value:function(W){var Z=this;this.listener=l()(W,"click",function(We){return Z.onClick(We)})}},{key:"onClick",value:function(W){var Z=W.delegateTarget||W.currentTarget,We=this.action(Z)||"copy",Gt=be({action:We,container:this.container,target:this.target(Z),text:this.text(Z)});this.emit(Gt?"success":"error",{action:We,text:Gt,trigger:Z,clearSelection:function(){Z&&Z.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(W){return Yt("action",W)}},{key:"defaultTarget",value:function(W){var Z=Yt("target",W);if(Z)return document.querySelector(Z)}},{key:"defaultText",value:function(W){return Yt("text",W)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(W){var Z=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return E(W,Z)}},{key:"cut",value:function(W){return h(W)}},{key:"isSupported",value:function(){var W=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],Z=typeof W=="string"?[W]:W,We=!!document.queryCommandSupported;return Z.forEach(function(Gt){We=We&&!!document.queryCommandSupported(Gt)}),We}}]),k})(s()),vr=Mt}),828:(function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,c){for(;s&&s.nodeType!==o;){if(typeof s.matches=="function"&&s.matches(c))return s;s=s.parentNode}}n.exports=a}),438:(function(n,o,i){var a=i(828);function s(u,p,d,m,h){var v=l.apply(this,arguments);return u.addEventListener(d,v,h),{destroy:function(){u.removeEventListener(d,v,h)}}}function c(u,p,d,m,h){return typeof u.addEventListener=="function"?s.apply(null,arguments):typeof d=="function"?s.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(v){return s(v,p,d,m,h)}))}function l(u,p,d,m){return function(h){h.delegateTarget=a(h.target,p),h.delegateTarget&&m.call(u,h)}}n.exports=c}),879:(function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}}),370:(function(n,o,i){var a=i(879),s=i(438);function c(d,m,h){if(!d&&!m&&!h)throw new Error("Missing required arguments");if(!a.string(m))throw new TypeError("Second argument must be a String");if(!a.fn(h))throw new TypeError("Third argument must be a Function");if(a.node(d))return l(d,m,h);if(a.nodeList(d))return u(d,m,h);if(a.string(d))return p(d,m,h);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function l(d,m,h){return d.addEventListener(m,h),{destroy:function(){d.removeEventListener(m,h)}}}function u(d,m,h){return Array.prototype.forEach.call(d,function(v){v.addEventListener(m,h)}),{destroy:function(){Array.prototype.forEach.call(d,function(v){v.removeEventListener(m,h)})}}}function p(d,m,h){return s(document.body,d,m,h)}n.exports=c}),817:(function(n){function o(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),l=document.createRange();l.selectNodeContents(i),c.removeAllRanges(),c.addRange(l),a=c.toString()}return a}n.exports=o}),279:(function(n){function o(){}o.prototype={on:function(i,a,s){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var c=this;function l(){c.off(i,l),a.apply(s,arguments)}return l._=a,this.on(i,l,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),c=0,l=s.length;for(c;c0&&i[i.length-1])&&(l[0]===6||l[0]===2)){r=0;continue}if(l[0]===3&&(!i||l[1]>i[0]&&l[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function te(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var n=r.call(e),o,i=[],a;try{for(;(t===void 0||t-- >0)&&!(o=n.next()).done;)i.push(o.value)}catch(s){a={error:s}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(a)throw a.error}}return i}function ne(e,t,r){if(r||arguments.length===2)for(var n=0,o=t.length,i;n1||c(m,v)})},h&&(o[m]=h(o[m])))}function c(m,h){try{l(n[m](h))}catch(v){d(i[0][3],v)}}function l(m){m.value instanceof kt?Promise.resolve(m.value.v).then(u,p):d(i[0][2],m)}function u(m){c("next",m)}function p(m){c("throw",m)}function d(m,h){m(h),i.shift(),i.length&&c(i[0][0],i[0][1])}}function zo(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof $e=="function"?$e(e):e[Symbol.iterator](),r={},n("next"),n("throw"),n("return"),r[Symbol.asyncIterator]=function(){return this},r);function n(i){r[i]=e[i]&&function(a){return new Promise(function(s,c){a=e[i](a),o(s,c,a.done,a.value)})}}function o(i,a,s,c){Promise.resolve(c).then(function(l){i({value:l,done:s})},a)}}function F(e){return typeof e=="function"}function Jt(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var Vr=Jt(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: `+r.map(function(n,o){return o+1+") "+n.toString()}).join(` `):"",this.name="UnsubscriptionError",this.errors=r}});function ct(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var rt=(function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=$e(a),c=s.next();!c.done;c=s.next()){var l=c.value;l.remove(this)}}catch(v){t={error:v}}finally{try{c&&!c.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var u=this.initialTeardown;if(F(u))try{u()}catch(v){i=v instanceof Vr?v.errors:[v]}var p=this._finalizers;if(p){this._finalizers=null;try{for(var d=$e(p),m=d.next();!m.done;m=d.next()){var h=m.value;try{qo(h)}catch(v){i=i!=null?i:[],v instanceof Vr?i=ne(ne([],te(i)),te(v.errors)):i.push(v)}}}catch(v){n={error:v}}finally{try{m&&!m.done&&(o=d.return)&&o.call(d)}finally{if(n)throw n.error}}}if(i)throw new Vr(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)qo(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&ct(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&ct(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=(function(){var t=new e;return t.closed=!0,t})(),e})();var Pn=rt.EMPTY;function zr(e){return e instanceof rt||e&&"closed"in e&&F(e.remove)&&F(e.add)&&F(e.unsubscribe)}function qo(e){F(e)?e():e.unsubscribe()}var Je={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var Xt={setTimeout:function(e,t){for(var r=[],n=2;n0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,a=o.isStopped,s=o.observers;return i||a?Pn:(this.currentObservers=null,s.push(r),new rt(function(){n.currentObservers=null,ct(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,a=n.isStopped;o?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new U;return r.source=this,r},t.create=function(r,n){return new Qo(r,n)},t})(U);var Qo=(function(e){ue(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:Pn},t})(I);var Un=(function(e){ue(t,e);function t(r){var n=e.call(this)||this;return n._value=r,n}return Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!1,configurable:!0}),t.prototype._subscribe=function(r){var n=e.prototype._subscribe.call(this,r);return!n.closed&&r.next(this._value),n},t.prototype.getValue=function(){var r=this,n=r.hasError,o=r.thrownError,i=r._value;if(n)throw o;return this._throwIfClosed(),i},t.prototype.next=function(r){e.prototype.next.call(this,this._value=r)},t})(I);var xr={now:function(){return(xr.delegate||Date).now()},delegate:void 0};var wr=(function(e){ue(t,e);function t(r,n,o){r===void 0&&(r=1/0),n===void 0&&(n=1/0),o===void 0&&(o=xr);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=n,i._timestampProvider=o,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=n===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n._buffer,a=n._infiniteTimeWindow,s=n._timestampProvider,c=n._windowTime;o||(i.push(r),!a&&i.push(s.now()+c)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o._infiniteTimeWindow,a=o._buffer,s=a.slice(),c=0;c0?e.prototype.schedule.call(this,r,n):(this.delay=n,this.state=r,this.scheduler.flush(this),this)},t.prototype.execute=function(r,n){return n>0||this.closed?e.prototype.execute.call(this,r,n):this._execute(r,n)},t.prototype.requestAsyncId=function(r,n,o){return o===void 0&&(o=0),o!=null&&o>0||o==null&&this.delay>0?e.prototype.requestAsyncId.call(this,r,n,o):(r.flush(this),0)},t})(tr);var ri=(function(e){ue(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t})(rr);var Wn=new ri(ti);var ni=(function(e){ue(t,e);function t(r,n){var o=e.call(this,r,n)||this;return o.scheduler=r,o.work=n,o}return t.prototype.requestAsyncId=function(r,n,o){return o===void 0&&(o=0),o!==null&&o>0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r._scheduled||(r._scheduled=er.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){var i;if(o===void 0&&(o=0),o!=null?o>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);var a=r.actions;n!=null&&n===r._scheduled&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==n&&(er.cancelAnimationFrame(n),r._scheduled=void 0)},t})(tr);var oi=(function(e){ue(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var n;r?n=r.id:(n=this._scheduled,this._scheduled=void 0);var o=this.actions,i;r=r||o.shift();do if(i=r.execute(r.state,r.delay))break;while((r=o[0])&&r.id===n&&o.shift());if(this._active=!1,i){for(;(r=o[0])&&r.id===n&&o.shift();)r.unsubscribe();throw i}},t})(rr);var je=new oi(ni);var y=new U(function(e){return e.complete()});function Br(e){return e&&F(e.schedule)}function Vn(e){return e[e.length-1]}function _t(e){return F(Vn(e))?e.pop():void 0}function qe(e){return Br(Vn(e))?e.pop():void 0}function Yr(e,t){return typeof Vn(e)=="number"?e.pop():t}var nr=(function(e){return e&&typeof e.length=="number"&&typeof e!="function"});function Gr(e){return F(e==null?void 0:e.then)}function Jr(e){return F(e[Qt])}function Xr(e){return Symbol.asyncIterator&&F(e==null?void 0:e[Symbol.asyncIterator])}function Zr(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Rc(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Qr=Rc();function en(e){return F(e==null?void 0:e[Qr])}function tn(e){return Vo(this,arguments,function(){var r,n,o,i;return Wr(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,kt(r.read())];case 3:return n=a.sent(),o=n.value,i=n.done,i?[4,kt(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,kt(o)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function rn(e){return F(e==null?void 0:e.getReader)}function q(e){if(e instanceof U)return e;if(e!=null){if(Jr(e))return jc(e);if(nr(e))return Fc(e);if(Gr(e))return Uc(e);if(Xr(e))return ii(e);if(en(e))return Nc(e);if(rn(e))return Dc(e)}throw Zr(e)}function jc(e){return new U(function(t){var r=e[Qt]();if(F(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function Fc(e){return new U(function(t){for(var r=0;r=2;return function(n){return n.pipe(e?L(function(o,i){return e(o,i,n)}):Oe,Me(1),r?ot(t):wi(function(){return new on}))}}function Gn(e){return e<=0?function(){return y}:S(function(t,r){var n=[];t.subscribe(T(r,function(o){n.push(o),e=2,!0))}function xe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new I}:t,n=e.resetOnError,o=n===void 0?!0:n,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,c=s===void 0?!0:s;return function(l){var u,p,d,m=0,h=!1,v=!1,x=function(){p==null||p.unsubscribe(),p=void 0},w=function(){x(),u=d=void 0,h=v=!1},E=function(){var _=u;w(),_==null||_.unsubscribe()};return S(function(_,de){m++,!v&&!h&&x();var be=d=d!=null?d:r();de.add(function(){m--,m===0&&!v&&!h&&(p=Jn(E,c))}),be.subscribe(de),!u&&m>0&&(u=new Ct({next:function(M){return be.next(M)},error:function(M){v=!0,x(),p=Jn(w,o,M),be.error(M)},complete:function(){h=!0,x(),p=Jn(w,a),be.complete()}}),q(_).subscribe(u))})(l)}}function Jn(e,t){for(var r=[],n=2;ne.next(document)),e}function P(e,t=document){return Array.from(t.querySelectorAll(e))}function G(e,t=document){let r=Le(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function Le(e,t=document){return t.querySelector(e)||void 0}function xt(){var e,t,r,n;return(n=(r=(t=(e=document.activeElement)==null?void 0:e.shadowRoot)==null?void 0:t.activeElement)!=null?r:document.activeElement)!=null?n:void 0}var il=R(b(document.body,"focusin"),b(document.body,"focusout")).pipe(Be(1),J(void 0),f(()=>xt()||document.body),se(1));function ir(e){return il.pipe(f(t=>e.contains(t)),ie())}function Ft(e,t){let{matches:r}=matchMedia("(hover)");return j(()=>(r?R(b(e,"mouseenter").pipe(f(()=>!0)),b(e,"mouseleave").pipe(f(()=>!1))):R(b(e,"touchstart").pipe(f(()=>!0)),b(e,"touchend").pipe(f(()=>!1)),b(e,"touchcancel").pipe(f(()=>!1)))).pipe(t?Tr(o=>Ve(+!o*t)):Oe,J(!0,e.matches(":hover"))))}function Oi(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Oi(e,r)}function A(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="undefined"&&(typeof t[o]!="boolean"?n.setAttribute(o,t[o]):n.setAttribute(o,""));for(let o of r)Oi(n,o);return n}function Li(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function ar(e){let t=A("script",{src:e});return j(()=>(document.head.appendChild(t),R(b(t,"load"),b(t,"error").pipe(g(()=>zn(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(f(()=>{}),V(()=>document.head.removeChild(t)),Me(1))))}var Mi=new I,al=j(()=>typeof ResizeObserver=="undefined"?ar("https://unpkg.com/resize-observer-polyfill"):Y(void 0)).pipe(f(()=>new ResizeObserver(e=>e.forEach(t=>Mi.next(t)))),g(e=>R(Ke,Y(e)).pipe(V(()=>e.disconnect()))),se(1));function Ae(e){return{width:e.offsetWidth,height:e.offsetHeight}}function Re(e){let t=e;for(;t.clientWidth===0&&t.parentElement;)t=t.parentElement;return al.pipe($(r=>r.observe(t)),g(r=>Mi.pipe(L(n=>n.target===t),V(()=>r.unobserve(t)))),f(()=>Ae(e)),J(Ae(e)))}function Mr(e){return{width:e.scrollWidth,height:e.scrollHeight}}function ki(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}function Ai(e){let t=[],r=e.parentElement;for(;r;)(e.clientWidth>r.clientWidth||e.clientHeight>r.clientHeight)&&t.push(r),r=(e=r).parentElement;return t.length===0&&t.push(document.documentElement),t}function wt(e){return{x:e.offsetLeft,y:e.offsetTop}}function Ci(e){let t=e.getBoundingClientRect();return{x:t.x+window.scrollX,y:t.y+window.scrollY}}function Hi(e){return R(b(window,"load"),b(window,"resize")).pipe(Xe(0,je),f(()=>wt(e)),J(wt(e)))}function ln(e){return{x:e.scrollLeft,y:e.scrollTop}}function Ut(e){return R(b(e,"scroll"),b(window,"scroll"),b(window,"resize")).pipe(Xe(0,je),f(()=>ln(e)),J(ln(e)))}var $i=new I,sl=j(()=>Y(new IntersectionObserver(e=>{for(let t of e)$i.next(t)},{threshold:0}))).pipe(g(e=>R(Ke,Y(e)).pipe(V(()=>e.disconnect()))),se(1));function Et(e){return sl.pipe($(t=>t.observe(e)),g(t=>$i.pipe(L(({target:r})=>r===e),V(()=>t.unobserve(e)),f(({isIntersecting:r})=>r))))}var cl=Object.create,la=Object.defineProperty,ll=Object.getOwnPropertyDescriptor,ul=Object.getOwnPropertyNames,pl=Object.getPrototypeOf,fl=Object.prototype.hasOwnProperty,ml=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),dl=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of ul(t))!fl.call(e,o)&&o!==r&&la(e,o,{get:()=>t[o],enumerable:!(n=ll(t,o))||n.enumerable});return e},hl=(e,t,r)=>(r=e!=null?cl(pl(e)):{},dl(t||!e||!e.__esModule?la(r,"default",{value:e,enumerable:!0}):r,e)),vl=ml((e,t)=>{var r="Expected a function",n=NaN,o="[object Symbol]",i=/^\s+|\s+$/g,a=/^[-+]0x[0-9a-f]+$/i,s=/^0b[01]+$/i,c=/^0o[0-7]+$/i,l=parseInt,u=typeof global=="object"&&global&&global.Object===Object&&global,p=typeof self=="object"&&self&&self.Object===Object&&self,d=u||p||Function("return this")(),m=Object.prototype,h=m.toString,v=Math.max,x=Math.min,w=function(){return d.Date.now()};function E(O,N,ee){var le,ce,Ne,bt,De,st,tt=0,Yt=!1,Mt=!1,vr=!0;if(typeof O!="function")throw new TypeError(r);N=M(N)||0,_(ee)&&(Yt=!!ee.leading,Mt="maxWait"in ee,Ne=Mt?v(M(ee.maxWait)||0,N):Ne,vr="trailing"in ee?!!ee.trailing:vr);function B(Te){var gt=le,br=ce;return le=ce=void 0,tt=Te,bt=O.apply(br,gt),bt}function C(Te){return tt=Te,De=setTimeout(W,N),Yt?B(Te):bt}function k(Te){var gt=Te-st,br=Te-tt,Ro=N-gt;return Mt?x(Ro,Ne-br):Ro}function D(Te){var gt=Te-st,br=Te-tt;return st===void 0||gt>=N||gt<0||Mt&&br>=Ne}function W(){var Te=w();if(D(Te))return Z(Te);De=setTimeout(W,k(Te))}function Z(Te){return De=void 0,vr&&le?B(Te):(le=ce=void 0,bt)}function We(){De!==void 0&&clearTimeout(De),tt=0,le=st=ce=De=void 0}function Gt(){return De===void 0?bt:Z(w())}function Nr(){var Te=w(),gt=D(Te);if(le=arguments,ce=this,st=Te,gt){if(De===void 0)return C(st);if(Mt)return De=setTimeout(W,N),B(st)}return De===void 0&&(De=setTimeout(W,N)),bt}return Nr.cancel=We,Nr.flush=Gt,Nr}function _(O){var N=typeof O;return!!O&&(N=="object"||N=="function")}function de(O){return!!O&&typeof O=="object"}function be(O){return typeof O=="symbol"||de(O)&&h.call(O)==o}function M(O){if(typeof O=="number")return O;if(be(O))return n;if(_(O)){var N=typeof O.valueOf=="function"?O.valueOf():O;O=_(N)?N+"":N}if(typeof O!="string")return O===0?O:+O;O=O.replace(i,"");var ee=s.test(O);return ee||c.test(O)?l(O.slice(2),ee?2:8):a.test(O)?n:+O}t.exports=E}),yn,K,ua,pa,Nt,Pi,fa,ma,da,lo,to,ro,bl,Ar={},ha=[],gl=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,Pr=Array.isArray;function pt(e,t){for(var r in t)e[r]=t[r];return e}function uo(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function Wt(e,t,r){var n,o,i,a={};for(i in t)i=="key"?n=t[i]:i=="ref"?o=t[i]:a[i]=t[i];if(arguments.length>2&&(a.children=arguments.length>3?yn.call(arguments,2):r),typeof e=="function"&&e.defaultProps!=null)for(i in e.defaultProps)a[i]===void 0&&(a[i]=e.defaultProps[i]);return fn(e,a,n,o,null)}function fn(e,t,r,n,o){var i={type:e,props:t,key:r,ref:n,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:o!=null?o:++ua,__i:-1,__u:0};return o==null&&K.vnode!=null&&K.vnode(i),i}function ft(e){return e.children}function at(e,t){this.props=e,this.context=t}function cr(e,t){if(t==null)return e.__?cr(e.__,e.__i+1):null;for(var r;ts&&Nt.sort(ma),e=Nt.shift(),s=Nt.length,e.__d&&(r=void 0,n=void 0,o=(n=(t=e).__v).__e,i=[],a=[],t.__P&&((r=pt({},n)).__v=n.__v+1,K.vnode&&K.vnode(r),po(t.__P,r,n,t.__n,t.__P.namespaceURI,32&n.__u?[o]:null,i,o!=null?o:cr(n),!!(32&n.__u),a),r.__v=n.__v,r.__.__k[r.__i]=r,_a(i,r,a),n.__e=n.__=null,r.__e!=o&&va(r)));vn.__r=0}function ba(e,t,r,n,o,i,a,s,c,l,u){var p,d,m,h,v,x,w,E=n&&n.__k||ha,_=t.length;for(c=_l(r,t,E,c,_),p=0;p<_;p++)(m=r.__k[p])!=null&&(d=m.__i==-1?Ar:E[m.__i]||Ar,m.__i=p,x=po(e,m,d,o,i,a,s,c,l,u),h=m.__e,m.ref&&d.ref!=m.ref&&(d.ref&&fo(d.ref,null,m),u.push(m.ref,m.__c||h,m)),v==null&&h!=null&&(v=h),(w=!!(4&m.__u))||d.__k===m.__k?c=ga(m,c,e,w):typeof m.type=="function"&&x!==void 0?c=x:h&&(c=h.nextSibling),m.__u&=-7);return r.__e=v,c}function _l(e,t,r,n,o){var i,a,s,c,l,u=r.length,p=u,d=0;for(e.__k=new Array(o),i=0;i0?fn(a.type,a.props,a.key,a.ref?a.ref:null,a.__v):a).__=e,a.__b=e.__b+1,s=null,(l=a.__i=yl(a,r,c,p))!=-1&&(p--,(s=r[l])&&(s.__u|=2)),s==null||s.__v==null?(l==-1&&(o>u?d--:oc?d--:d++,a.__u|=4))):e.__k[i]=null;if(p)for(i=0;i(u?1:0)){for(o=r-1,i=r+1;o>=0||i=0?o--:i++])!=null&&!(2&l.__u)&&s==l.key&&c==l.type)return a}return-1}function Ri(e,t,r){t[0]=="-"?e.setProperty(t,r!=null?r:""):e[t]=r==null?"":typeof r!="number"||gl.test(t)?r:r+"px"}function un(e,t,r,n,o){var i,a;e:if(t=="style")if(typeof r=="string")e.style.cssText=r;else{if(typeof n=="string"&&(e.style.cssText=n=""),n)for(t in n)r&&t in r||Ri(e.style,t,"");if(r)for(t in r)n&&r[t]==n[t]||Ri(e.style,t,r[t])}else if(t[0]=="o"&&t[1]=="n")i=t!=(t=t.replace(da,"$1")),a=t.toLowerCase(),t=a in e||t=="onFocusOut"||t=="onFocusIn"?a.slice(2):t.slice(2),e.l||(e.l={}),e.l[t+i]=r,r?n?r.u=n.u:(r.u=lo,e.addEventListener(t,i?ro:to,i)):e.removeEventListener(t,i?ro:to,i);else{if(o=="http://www.w3.org/2000/svg")t=t.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(t!="width"&&t!="height"&&t!="href"&&t!="list"&&t!="form"&&t!="tabIndex"&&t!="download"&&t!="rowSpan"&&t!="colSpan"&&t!="role"&&t!="popover"&&t in e)try{e[t]=r!=null?r:"";break e}catch(s){}typeof r=="function"||(r==null||r===!1&&t[4]!="-"?e.removeAttribute(t):e.setAttribute(t,t=="popover"&&r==1?"":r))}}function ji(e){return function(t){if(this.l){var r=this.l[t.type+e];if(t.t==null)t.t=lo++;else if(t.t0?e:Pr(e)?e.map(ya):pt({},e)}function xl(e,t,r,n,o,i,a,s,c){var l,u,p,d,m,h,v,x=r.props,w=t.props,E=t.type;if(E=="svg"?o="http://www.w3.org/2000/svg":E=="math"?o="http://www.w3.org/1998/Math/MathML":o||(o="http://www.w3.org/1999/xhtml"),i!=null){for(l=0;l=r.__.length&&r.__.push({}),r.__[e]}function bn(e){return $r=1,Tl(Ta,e)}function Tl(e,t,r){var n=mo(Hr++,2);if(n.t=e,!n.__c&&(n.__=[r?r(t):Ta(void 0,t),function(s){var c=n.__N?n.__N[0]:n.__[0],l=n.t(c,s);c!==l&&(n.__N=[l,n.__[1]],n.__c.setState({}))}],n.__c=ve,!ve.__f)){var o=function(s,c,l){if(!n.__c.__H)return!0;var u=n.__c.__H.__.filter(function(d){return!!d.__c});if(u.every(function(d){return!d.__N}))return!i||i.call(this,s,c,l);var p=n.__c.props!==s;return u.forEach(function(d){if(d.__N){var m=d.__[0];d.__=d.__N,d.__N=void 0,m!==d.__[0]&&(p=!0)}}),i&&i.call(this,s,c,l)||p};ve.__f=!0;var i=ve.shouldComponentUpdate,a=ve.componentWillUpdate;ve.componentWillUpdate=function(s,c,l){if(this.__e){var u=i;i=void 0,o(s,c,l),i=u}a&&a.call(this,s,c,l)},ve.shouldComponentUpdate=o}return n.__N||n.__}function mt(e,t){var r=mo(Hr++,3);!we.__s&&Ea(r.__H,t)&&(r.__=e,r.u=t,ve.__H.__h.push(r))}function Vt(e){return $r=5,ur(function(){return{current:e}},[])}function ur(e,t){var r=mo(Hr++,7);return Ea(r.__H,t)&&(r.__=e(),r.__H=t,r.__h=e),r.__}function Sl(e,t){return $r=8,ur(function(){return e},t)}function Ol(){for(var e;e=wa.shift();)if(e.__P&&e.__H)try{e.__H.__h.forEach(mn),e.__H.__h.forEach(oo),e.__H.__h=[]}catch(t){e.__H.__h=[],we.__e(t,e.__v)}}we.__b=function(e){ve=null,Ui&&Ui(e)},we.__=function(e,t){e&&t.__k&&t.__k.__m&&(e.__m=t.__k.__m),zi&&zi(e,t)},we.__r=function(e){Ni&&Ni(e),Hr=0;var t=(ve=e.__c).__H;t&&(Zn===ve?(t.__h=[],ve.__h=[],t.__.forEach(function(r){r.__N&&(r.__=r.__N),r.u=r.__N=void 0})):(t.__h.forEach(mn),t.__h.forEach(oo),t.__h=[],Hr=0)),Zn=ve},we.diffed=function(e){Di&&Di(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&(wa.push(t)!==1&&Fi===we.requestAnimationFrame||((Fi=we.requestAnimationFrame)||Ll)(Ol)),t.__H.__.forEach(function(r){r.u&&(r.__H=r.u),r.u=void 0})),Zn=ve=null},we.__c=function(e,t){t.some(function(r){try{r.__h.forEach(mn),r.__h=r.__h.filter(function(n){return!n.__||oo(n)})}catch(n){t.some(function(o){o.__h&&(o.__h=[])}),t=[],we.__e(n,r.__v)}}),Wi&&Wi(e,t)},we.unmount=function(e){Vi&&Vi(e);var t,r=e.__c;r&&r.__H&&(r.__H.__.forEach(function(n){try{mn(n)}catch(o){t=o}}),r.__H=void 0,t&&we.__e(t,r.__v))};var qi=typeof requestAnimationFrame=="function";function Ll(e){var t,r=function(){clearTimeout(n),qi&&cancelAnimationFrame(t),setTimeout(e)},n=setTimeout(r,35);qi&&(t=requestAnimationFrame(r))}function mn(e){var t=ve,r=e.__c;typeof r=="function"&&(e.__c=void 0,r()),ve=t}function oo(e){var t=ve;e.__c=e.__(),ve=t}function Ea(e,t){return!e||e.length!==t.length||t.some(function(r,n){return r!==e[n]})}function Ta(e,t){return typeof t=="function"?t(e):t}function Ml(e,t){for(var r in t)e[r]=t[r];return e}function Ki(e,t){for(var r in e)if(r!=="__source"&&!(r in t))return!0;for(var n in t)if(n!=="__source"&&e[n]!==t[n])return!0;return!1}function Bi(e,t){this.props=e,this.context=t}(Bi.prototype=new at).isPureReactComponent=!0,Bi.prototype.shouldComponentUpdate=function(e,t){return Ki(this.props,e)||Ki(this.state,t)};var Yi=K.__b;K.__b=function(e){e.type&&e.type.__f&&e.ref&&(e.props.ref=e.ref,e.ref=null),Yi&&Yi(e)};var Yx=typeof Symbol<"u"&&Symbol.for&&Symbol.for("react.forward_ref")||3911,kl=K.__e;K.__e=function(e,t,r,n){if(e.then){for(var o,i=t;i=i.__;)if((o=i.__c)&&o.__c)return t.__e==null&&(t.__e=r.__e,t.__k=r.__k),o.__c(e,t)}kl(e,t,r,n)};var Gi=K.unmount;function Sa(e,t,r){return e&&(e.__c&&e.__c.__H&&(e.__c.__H.__.forEach(function(n){typeof n.__c=="function"&&n.__c()}),e.__c.__H=null),(e=Ml({},e)).__c!=null&&(e.__c.__P===r&&(e.__c.__P=t),e.__c.__e=!0,e.__c=null),e.__k=e.__k&&e.__k.map(function(n){return Sa(n,t,r)})),e}function Oa(e,t,r){return e&&r&&(e.__v=null,e.__k=e.__k&&e.__k.map(function(n){return Oa(n,t,r)}),e.__c&&e.__c.__P===t&&(e.__e&&r.appendChild(e.__e),e.__c.__e=!0,e.__c.__P=r)),e}function Qn(){this.__u=0,this.o=null,this.__b=null}function La(e){var t=e.__.__c;return t&&t.__a&&t.__a(e)}function pn(){this.i=null,this.l=null}K.unmount=function(e){var t=e.__c;t&&t.__R&&t.__R(),t&&32&e.__u&&(e.type=null),Gi&&Gi(e)},(Qn.prototype=new at).__c=function(e,t){var r=t.__c,n=this;n.o==null&&(n.o=[]),n.o.push(r);var o=La(n.__v),i=!1,a=function(){i||(i=!0,r.__R=null,o?o(s):s())};r.__R=a;var s=function(){if(!--n.__u){if(n.state.__a){var c=n.state.__a;n.__v.__k[0]=Oa(c,c.__c.__P,c.__c.__O)}var l;for(n.setState({__a:n.__b=null});l=n.o.pop();)l.forceUpdate()}};n.__u++||32&t.__u||n.setState({__a:n.__b=n.__v.__k[0]}),e.then(a,a)},Qn.prototype.componentWillUnmount=function(){this.o=[]},Qn.prototype.render=function(e,t){if(this.__b){if(this.__v.__k){var r=document.createElement("div"),n=this.__v.__k[0].__c;this.__v.__k[0]=Sa(this.__b,r,n.__O=n.__P)}this.__b=null}var o=t.__a&&Wt(ft,null,e.fallback);return o&&(o.__u&=-33),[Wt(ft,null,t.__a?null:e.children),o]};var Ji=function(e,t,r){if(++r[1]===r[0]&&e.l.delete(t),e.props.revealOrder&&(e.props.revealOrder[0]!=="t"||!e.l.size))for(r=e.i;r;){for(;r.length>3;)r.pop()();if(r[1]Object.freeze({get current(){return t.current}}),[])}var Nl=typeof globalThis<"u"&&typeof navigator<"u"&&typeof document<"u";function Dl(e,...t){var r;(r=e==null?void 0:e.addEventListener)==null||r.call(e,...t)}function Wl(e,...t){var r;(r=e==null?void 0:e.removeEventListener)==null||r.call(e,...t)}var Vl=(e,t)=>Object.hasOwn(e,t),zl=()=>!0,ql=()=>!1;function Kl(e=!1){let t=Vt(e),r=Sl(()=>t.current,[]);return mt(()=>(t.current=!0,()=>{t.current=!1}),[]),r}function Bl(e,...t){let r=Kl(),n=ka(t[1]),o=ur(()=>function(...i){r()&&(typeof n.current=="function"?n.current.apply(this,i):typeof n.current.handleEvent=="function"&&n.current.handleEvent.apply(this,i))},[]);mt(()=>{let i=Yl(e)?e.current:e;if(!i)return;let a=t.slice(2);return Dl(i,t[0],o,...a),()=>{Wl(i,t[0],o,...a)}},[e,t[0]])}function Yl(e){return e!==null&&typeof e=="object"&&Vl(e,"current")}var Gl=e=>typeof e=="function"?e:typeof e=="string"?t=>t.key===e:e?zl:ql,Jl=Nl?globalThis:null;function Aa(e,t,r=[],n={}){let{event:o="keydown",target:i=Jl,eventOptions:a}=n,s=ka(t),c=ur(()=>{let l=Gl(e);return function(u){l(u)&&s.current.call(this,u)}},r);Bl(i,o,c,a)}function Ca(e){var t,r,n="";if(typeof e=="string"||typeof e=="number")n+=e;else if(typeof e=="object")if(Array.isArray(e)){var o=e.length;for(t=0;t1)St--;else{for(var e,t=!1;kr!==void 0;){var r=kr;for(kr=void 0,io++;r!==void 0;){var n=r.o;if(r.o=void 0,r.f&=-3,!(8&r.f)&&Pa(r))try{r.c()}catch(o){t||(e=o,t=!0)}r=n}}if(io=0,St--,t)throw e}}function Ql(e){if(St>0)return e();St++;try{return e()}finally{xn()}}var ae=void 0;function Ha(e){var t=ae;ae=void 0;try{return e()}finally{ae=t}}var kr=void 0,St=0,io=0,gn=0;function $a(e){if(ae!==void 0){var t=e.n;if(t===void 0||t.t!==ae)return t={i:0,S:e,p:ae.s,n:void 0,t:ae,e:void 0,x:void 0,r:t},ae.s!==void 0&&(ae.s.n=t),ae.s=t,e.n=t,32&ae.f&&e.S(t),t;if(t.i===-1)return t.i=0,t.n!==void 0&&(t.n.p=t.p,t.p!==void 0&&(t.p.n=t.n),t.p=ae.s,t.n=void 0,ae.s.n=t,ae.s=t),t}}function Ce(e,t){this.v=e,this.i=0,this.n=void 0,this.t=void 0,this.W=t==null?void 0:t.watched,this.Z=t==null?void 0:t.unwatched,this.name=t==null?void 0:t.name}Ce.prototype.brand=Zl;Ce.prototype.h=function(){return!0};Ce.prototype.S=function(e){var t=this,r=this.t;r!==e&&e.e===void 0&&(e.x=r,this.t=e,r!==void 0?r.e=e:Ha(function(){var n;(n=t.W)==null||n.call(t)}))};Ce.prototype.U=function(e){var t=this;if(this.t!==void 0){var r=e.e,n=e.x;r!==void 0&&(r.x=n,e.e=void 0),n!==void 0&&(n.e=r,e.x=void 0),e===this.t&&(this.t=n,n===void 0&&Ha(function(){var o;(o=t.Z)==null||o.call(t)}))}};Ce.prototype.subscribe=function(e){var t=this;return qt(function(){var r=t.value,n=ae;ae=void 0;try{e(r)}finally{ae=n}},{name:"sub"})};Ce.prototype.valueOf=function(){return this.value};Ce.prototype.toString=function(){return this.value+""};Ce.prototype.toJSON=function(){return this.value};Ce.prototype.peek=function(){var e=ae;ae=void 0;try{return this.value}finally{ae=e}};Object.defineProperty(Ce.prototype,"value",{get:function(){var e=$a(this);return e!==void 0&&(e.i=this.i),this.v},set:function(e){if(e!==this.v){if(io>100)throw new Error("Cycle detected");this.v=e,this.i++,gn++,St++;try{for(var t=this.t;t!==void 0;t=t.x)t.t.N()}finally{xn()}}}});function Ot(e,t){return new Ce(e,t)}function Pa(e){for(var t=e.s;t!==void 0;t=t.n)if(t.S.i!==t.i||!t.S.h()||t.S.i!==t.i)return!0;return!1}function Ia(e){for(var t=e.s;t!==void 0;t=t.n){var r=t.S.n;if(r!==void 0&&(t.r=r),t.S.n=t,t.i=-1,t.n===void 0){e.s=t;break}}}function Ra(e){for(var t=e.s,r=void 0;t!==void 0;){var n=t.p;t.i===-1?(t.S.U(t),n!==void 0&&(n.n=t.n),t.n!==void 0&&(t.n.p=n)):r=t,t.S.n=t.r,t.r!==void 0&&(t.r=void 0),t=n}e.s=r}function Kt(e,t){Ce.call(this,void 0),this.x=e,this.s=void 0,this.g=gn-1,this.f=4,this.W=t==null?void 0:t.watched,this.Z=t==null?void 0:t.unwatched,this.name=t==null?void 0:t.name}Kt.prototype=new Ce;Kt.prototype.h=function(){if(this.f&=-3,1&this.f)return!1;if((36&this.f)==32||(this.f&=-5,this.g===gn))return!0;if(this.g=gn,this.f|=1,this.i>0&&!Pa(this))return this.f&=-2,!0;var e=ae;try{Ia(this),ae=this;var t=this.x();(16&this.f||this.v!==t||this.i===0)&&(this.v=t,this.f&=-17,this.i++)}catch(r){this.v=r,this.f|=16,this.i++}return ae=e,Ra(this),this.f&=-2,!0};Kt.prototype.S=function(e){if(this.t===void 0){this.f|=36;for(var t=this.s;t!==void 0;t=t.n)t.S.S(t)}Ce.prototype.S.call(this,e)};Kt.prototype.U=function(e){if(this.t!==void 0&&(Ce.prototype.U.call(this,e),this.t===void 0)){this.f&=-33;for(var t=this.s;t!==void 0;t=t.n)t.S.U(t)}};Kt.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(var e=this.t;e!==void 0;e=e.x)e.t.N()}};Object.defineProperty(Kt.prototype,"value",{get:function(){if(1&this.f)throw new Error("Cycle detected");var e=$a(this);if(this.h(),e!==void 0&&(e.i=this.i),16&this.f)throw this.v;return this.v}});function ta(e,t){return new Kt(e,t)}function ja(e){var t=e.u;if(e.u=void 0,typeof t=="function"){St++;var r=ae;ae=void 0;try{t()}catch(n){throw e.f&=-2,e.f|=8,ho(e),n}finally{ae=r,xn()}}}function ho(e){for(var t=e.s;t!==void 0;t=t.n)t.S.U(t);e.x=void 0,e.s=void 0,ja(e)}function eu(e){if(ae!==this)throw new Error("Out-of-order effect");Ra(this),ae=e,this.f&=-2,8&this.f&&ho(this),xn()}function pr(e,t){this.x=e,this.u=void 0,this.s=void 0,this.o=void 0,this.f=32,this.name=t==null?void 0:t.name}pr.prototype.c=function(){var e=this.S();try{if(8&this.f||this.x===void 0)return;var t=this.x();typeof t=="function"&&(this.u=t)}finally{e()}};pr.prototype.S=function(){if(1&this.f)throw new Error("Cycle detected");this.f|=1,this.f&=-9,ja(this),Ia(this),St++;var e=ae;return ae=this,eu.bind(this,e)};pr.prototype.N=function(){2&this.f||(this.f|=2,this.o=kr,kr=this)};pr.prototype.d=function(){this.f|=8,1&this.f||ho(this)};pr.prototype.dispose=function(){this.d()};function qt(e,t){var r=new pr(e,t);try{r.c()}catch(o){throw r.d(),o}var n=r.d.bind(r);return n[Symbol.dispose]=n,n}var Fa,vo,eo,Ua=[];qt(function(){Fa=this.N})();function fr(e,t){K[e]=t.bind(null,K[e]||function(){})}function _n(e){eo&&eo(),eo=e&&e.S()}function Na(e){var t=this,r=e.data,n=ru(r);n.value=r;var o=ur(function(){for(var s=t,c=t.__v;c=c.__;)if(c.__c){c.__c.__$f|=4;break}var l=ta(function(){var m=n.value.value;return m===0?0:m===!0?"":m||""}),u=ta(function(){return!Array.isArray(l.value)&&!pa(l.value)}),p=qt(function(){if(this.N=Da,u.value){var m=l.value;s.__v&&s.__v.__e&&s.__v.__e.nodeType===3&&(s.__v.__e.data=m)}}),d=t.__$u.d;return t.__$u.d=function(){p(),d.call(this)},[u,l]},[]),i=o[0],a=o[1];return i.value?a.peek():a.value}Na.displayName="ReactiveTextNode";Object.defineProperties(Ce.prototype,{constructor:{configurable:!0,value:void 0},type:{configurable:!0,value:Na},props:{configurable:!0,get:function(){return{data:this}}},__b:{configurable:!0,value:1}});fr("__b",function(e,t){if(typeof t.type=="function"&&typeof window<"u"&&window.__PREACT_SIGNALS_DEVTOOLS__&&window.__PREACT_SIGNALS_DEVTOOLS__.exitComponent(),typeof t.type=="string"){var r,n=t.props;for(var o in n)if(o!=="children"){var i=n[o];i instanceof Ce&&(r||(t.__np=r={}),r[o]=i,n[o]=i.peek())}}e(t)});fr("__r",function(e,t){if(typeof t.type=="function"&&typeof window<"u"&&window.__PREACT_SIGNALS_DEVTOOLS__&&window.__PREACT_SIGNALS_DEVTOOLS__.enterComponent(t),t.type!==ft){_n();var r,n=t.__c;n&&(n.__$f&=-2,(r=n.__$u)===void 0&&(n.__$u=r=(function(o){var i;return qt(function(){i=this}),i.c=function(){n.__$f|=1,n.setState({})},i})())),vo=n,_n(r)}e(t)});fr("__e",function(e,t,r,n){typeof window<"u"&&window.__PREACT_SIGNALS_DEVTOOLS__&&window.__PREACT_SIGNALS_DEVTOOLS__.exitComponent(),_n(),vo=void 0,e(t,r,n)});fr("diffed",function(e,t){typeof t.type=="function"&&typeof window<"u"&&window.__PREACT_SIGNALS_DEVTOOLS__&&window.__PREACT_SIGNALS_DEVTOOLS__.exitComponent(),_n(),vo=void 0;var r;if(typeof t.type=="string"&&(r=t.__e)){var n=t.__np,o=t.props;if(n){var i=r.U;if(i)for(var a in i){var s=i[a];s!==void 0&&!(a in n)&&(s.d(),i[a]=void 0)}else i={},r.U=i;for(var c in n){var l=i[c],u=n[c];l===void 0?(l=tu(r,c,u,o),i[c]=l):l.o(u,o)}}}e(t)});function tu(e,t,r,n){var o=t in e&&e.ownerSVGElement===void 0,i=Ot(r);return{o:function(a,s){i.value=a,n=s},d:qt(function(){this.N=Da;var a=i.value.value;n[t]!==a&&(n[t]=a,o?e[t]=a:a?e.setAttribute(t,a):e.removeAttribute(t))})}}fr("unmount",function(e,t){if(typeof t.type=="string"){var r=t.__e;if(r){var n=r.U;if(n){r.U=void 0;for(var o in n){var i=n[o];i&&i.d()}}}}else{var a=t.__c;if(a){var s=a.__$u;s&&(a.__$u=void 0,s.d())}}e(t)});fr("__h",function(e,t,r,n){(n<3||n===9)&&(t.__$f|=2),e(t,r,n)});at.prototype.shouldComponentUpdate=function(e,t){var r=this.__$u,n=r&&r.s!==void 0;for(var o in t)return!0;if(this.__f||typeof this.u=="boolean"&&this.u===!0){var i=2&this.__$f;if(!(n||i||4&this.__$f)||1&this.__$f)return!0}else if(!(n||4&this.__$f)||3&this.__$f)return!0;for(var a in e)if(a!=="__source"&&e[a]!==this.props[a])return!0;for(var s in this.props)if(!(s in e))return!0;return!1};function ru(e,t){return bn(function(){return Ot(e,t)})[0]}var nu=function(e){queueMicrotask(function(){queueMicrotask(e)})};function ou(){Ql(function(){for(var e;e=Ua.shift();)Fa.call(e)})}function Da(){Ua.push(this)===1&&(K.requestAnimationFrame||nu)(ou)}var ao=[0];for(let e=0;e<32;e++)ao.push(ao[e]|1<>>5]>>>e&1}set(e){this.data[e>>>5]|=1<<(e&31)}forEach(e){let t=this.size&31;for(let r=0;r{var r;return(r=t.tags)==null?void 0:r.length})&&(matchMedia("(max-width: 768px)").matches||Wa())}function Dt(){Qe.value=He(H({},Qe.value),{hideSearch:!Qe.value.hideSearch})}function Wa(){Qe.value=He(H({},Qe.value),{hideFilters:!Qe.value.hideFilters})}function dn(){return Qe.value.selectedItem}function so(e){Qe.value=He(H({},Qe.value),{selectedItem:e})}function su(){var e,t;return(t=(e=lr.value)==null?void 0:e.items)!=null?t:[]}function wn(){return typeof Se.value.input=="string"?Se.value.input:""}function Va(e){let t=za();e.length&&!t.length?Se.value=He(H({},Se.value),{page:void 0,input:e}):!e.length&&t.length?Se.value=He(H({},Se.value),{page:void 0,input:{type:"operator",data:{operator:"not",operands:[]}}}):Se.value=He(H({},Se.value),{page:void 0,input:e})}function cu(){typeof it.value.pagination.next<"u"&&(Se.value=He(H({},Se.value),{page:it.value.pagination.next}))}function lu(e){let t=Se.value.filter.input;if("type"in t&&t.type==="operator"){for(let r of t.data.operands)if("type"in r&&r.type==="value"&&typeof r.data.value=="string"&&r.data.value===e)return!0}return!1}function za(){let e=Se.value.filter.input,t=[];if("type"in e&&e.type==="operator")for(let r of e.data.operands)"type"in r&&r.type==="value"&&typeof r.data.value=="string"&&t.push(r.data.value);return t}function uu(e){let t=Se.value.filter.input,r=[];if("type"in t&&t.type==="operator")for(let n of t.data.operands)"type"in n&&n.type==="value"&&typeof n.data.value=="string"&&r.push(n.data.value);if(r.includes(e)){let n=r.indexOf(e);n>-1&&r.splice(n,1)}else r.push(e);Se.value=He(H({},Se.value),{page:void 0,filter:He(H({},Se.value.filter),{input:{type:"operator",data:{operator:"and",operands:r.map(n=>({type:"value",data:{field:"tags",value:n}}))}}})}),Va(wn())}function pu(){return it.value.items}function fu(){return it.value.total}function mu(){var e;for(let t of(e=it.value.aggregations)!=null?e:[])if(t.type==="term")return t.data.value;return[]}function sr(){return Qe.value.hideSearch}function du(){return Qe.value.hideFilters}function qa(){var e;return(e=Ka.value.highlight)!=null?e:!1}var Qe=Ot({hideSearch:!0,hideFilters:!0,selectedItem:0}),Ka=Ot({}),lr=Ot(),na=Ot(),Se=Ot({input:"",filter:{input:{type:"operator",data:{operator:"and",operands:[]}},aggregation:{input:[{type:"term",data:{field:"tags"}}]}}}),it=Ot({items:[],query:{select:{documents:new ra(0),terms:new ra(0)},values:[]},pagination:{total:0}});function hu(e,t,r){for(let n=0;tr&&t(0,o,r,r=i);continue;case 62:e.charCodeAt(r+1)===47?t(2,--o,r,r=i+1):hu(e,r,n)?t(3,o,r,r=i+1):t(1,o++,r,r=i+1)}i>r&&t(0,o,r,i)}function bu(e,t=0,r=e.length){let n=++t;e:for(let l=0;n{let i=[],a=[],{onElement:s,onText:c=gu}=typeof r=="function"?{onElement:r}:r,l=0,u=0;return e(t,(p,d,m,h)=>{if(p===0)i[l++]=c(t,m,h),a[u++]={value:null,depth:d};else if(p&1&&(a[u++]={value:bu(t,m,h),depth:d}),p&2)for(let v=0;u>=0;v++){let{value:x,depth:w}=a[--u];if(w>d)continue;let E=i.slice(l-=v,l+v);i[l++]=s(x,E),u++;break}},n,o),i.slice(0,l)}}function yu(e){return e.replace(/[&<>]/g,t=>{switch(t.charCodeAt(0)){case 38:return"&";case 60:return"<";case 62:return">"}})}function hn(e){return e.replace(/&(amp|[lg]t);/g,t=>{switch(t.charCodeAt(1)){case 97:return"&";case 108:return"<";case 103:return">"}})}function xu(e,t){return{start:e.start+t,end:e.end+t,value:e.value}}function wu(e,t,r){return e.slice(t,r)}function Eu(e){let{onHighlight:t,onText:r=wu}=typeof e=="function"?{onHighlight:e}:e;return(n,o,i=0,a=n.length)=>{var l;let s=[],c=(l=o==null?void 0:o.ranges)!=null?l:[];for(let u=0,p=i;ua)break;let m=c[u].end;if(mi&&s.push(r(n,i,d));let{value:h}=c[u];s.push(t(n,{start:d,end:i=m,value:h}))}return i{let o=n.data;switch(o.type){case 1:na.value=!0;break;case 3:typeof o.data.pagination.prev<"u"?it.value=He(H({},it.value),{pagination:o.data.pagination,items:[...it.value.items,...o.data.items]}):(it.value=o.data,so(0));break}},qt(()=>{lr.value&&r.postMessage({type:0,data:lr.value})}),qt(()=>{na.value&&r.postMessage({type:2,data:Se.value})})}var oa={container:"p",hidden:"m"};function ku(e){return z("div",{class:zt(oa.container,{[oa.hidden]:e.hidden}),onClick:()=>Dt()})}var ia={container:"r",disabled:"c"};function co(e){return z("button",{class:zt(ia.container,{[ia.disabled]:!e.onClick}),onClick:e.onClick,children:e.children})}var aa=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),Au=e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(t,r,n)=>n?n.toUpperCase():r.toLowerCase()),sa=e=>{let t=Au(e);return t.charAt(0).toUpperCase()+t.slice(1)},Cu=(...e)=>e.filter((t,r,n)=>!!t&&t.trim()!==""&&n.indexOf(t)===r).join(" ").trim(),Hu={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},$u=c=>{var l=c,{color:e="currentColor",size:t=24,strokeWidth:r=2,absoluteStrokeWidth:n,children:o,iconNode:i,class:a=""}=l,s=gr(l,["color","size","strokeWidth","absoluteStrokeWidth","children","iconNode","class"]);return Wt("svg",H(He(H({},Hu),{width:String(t),height:t,stroke:e,"stroke-width":n?Number(r)*24/Number(t):r,class:["lucide",a].join(" ")}),s),[...i.map(([u,p])=>Wt(u,p)),...Cr(o)])},bo=(e,t)=>{let r=a=>{var s=a,{class:n="",children:o}=s,i=gr(s,["class","children"]);return Wt($u,He(H({},i),{iconNode:t,class:Cu(`lucide-${aa(sa(e))}`,`lucide-${aa(e)}`,n)}),o)};return r.displayName=sa(e),r},Pu=bo("corner-down-left",[["path",{d:"M20 4v7a4 4 0 0 1-4 4H4",key:"6o5b7l"}],["path",{d:"m9 10-5 5 5 5",key:"1kshq7"}]]),Iu=bo("list-filter",[["path",{d:"M2 5h20",key:"1fs1ex"}],["path",{d:"M6 12h12",key:"8npq4p"}],["path",{d:"M9 19h6",key:"456am0"}]]),Ru=bo("search",[["path",{d:"m21 21-4.34-4.34",key:"14j7rj"}],["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}]]),Gx=hl(vl(),1);function ju({threshold:e=0,root:t=null,rootMargin:r="0%",freezeOnceVisible:n=!1,initialIsIntersecting:o=!1,onChange:i}={}){var a;let[s,c]=bn(null),[l,u]=bn(()=>({isIntersecting:o,entry:void 0})),p=Vt();p.current=i;let d=((a=l.entry)==null?void 0:a.isIntersecting)&&n;mt(()=>{if(!s||!("IntersectionObserver"in window)||d)return;let v,x=new IntersectionObserver(w=>{let E=Array.isArray(x.thresholds)?x.thresholds:[x.thresholds];w.forEach(_=>{let de=_.isIntersecting&&E.some(be=>_.intersectionRatio>=be);u({isIntersecting:de,entry:_}),p.current&&p.current(de,_),de&&n&&v&&(v(),v=void 0)})},{threshold:e,root:t,rootMargin:r});return x.observe(s),()=>{x.disconnect()}},[s,JSON.stringify(e),t,r,d,n]);let m=Vt(null);mt(()=>{var v;!s&&(v=l.entry)!=null&&v.target&&!n&&!d&&m.current!==l.entry.target&&(m.current=l.entry.target,u({isIntersecting:o,entry:void 0}))},[s,l.entry,n,d,o]);let h=[c,!!l.isIntersecting,l.entry];return h.ref=h[0],h.isIntersecting=h[1],h.entry=h[2],h}var lt={container:"n",hidden:"l",content:"u",pop:"d",badge:"y",sidebar:"i",controls:"w",results:"k",loadmore:"z"};function Fu(e){let{isIntersecting:t,ref:r}=ju({threshold:0});mt(()=>{t&&cu()},[t]);let n=Vt(null);mt(()=>{n.current&&typeof Se.value.page>"u"&&n.current.scrollTo({top:0,behavior:"smooth"})},[Se.value]);let o=za();return z("div",{class:zt(lt.container,{[lt.hidden]:e.hidden}),children:[z("div",{class:lt.content,children:[z("div",{class:lt.controls,children:[z(co,{onClick:Dt,children:z(Ru,{})}),z(Nu,{focus:!e.hidden}),z(co,{onClick:Wa,children:[z(Iu,{}),o.length>0&&z("span",{class:lt.badge,children:o.length})]})]}),z("div",{class:lt.results,ref:n,children:[z(Du,{keyboard:!e.hidden}),z("div",{class:lt.loadmore,ref:r})]})]}),z("div",{class:zt(lt.sidebar,{[lt.hidden]:du()}),children:z(Uu,{})})]})}var Tt={container:"X",list:"j",heading:"F",title:"I",item:"o",active:"g",value:"R",count:"q"};function Uu(e){let t=mu();return t.sort((r,n)=>n.node.count-r.node.count),z("div",{class:Tt.container,children:[z("h3",{class:Tt.heading,children:"Filters"}),z("h4",{class:Tt.title,children:"Tags"}),z("ol",{class:Tt.list,children:t.map(r=>z("li",{class:zt(Tt.item,{[Tt.active]:lu(r.node.value)}),onClick:()=>uu(r.node.value),children:[z("span",{class:Tt.value,children:r.node.value}),z("span",{class:Tt.count,children:r.node.count})]}))})]})}var ca={container:"f"};function Nu(e){let t=Vt(null);return mt(()=>{var r,n;e.focus?(r=t.current)==null||r.focus():(n=t.current)==null||n.blur()},[e.focus]),z("div",{class:ca.container,children:z("input",{ref:t,type:"text",class:ca.content,value:hn(wn()),onInput:r=>Va(yu(r.currentTarget.value)),autocapitalize:"off",autocomplete:"off",autocorrect:"off",placeholder:"Search",spellcheck:!1,role:"combobox"})})}var ut={container:"b",heading:"A",item:"a",active:"h",wrapper:"B",actions:"s",title:"x",path:"t"};function Ga(){let[e,t]=bn(!1);return mt(()=>{let r=()=>t(!0),n=()=>t(!1);return document.addEventListener("compositionstart",r),document.addEventListener("compositionend",n),()=>{document.removeEventListener("compositionstart",r),document.removeEventListener("compositionend",n)}},[]),e}function Du(e){var s;let t=su(),r=pu(),n=dn(),o=Vt([]),i=Ga();mt(()=>{let c=o.current[n];c&&c.scrollIntoView({block:"center",behavior:"smooth"})},[n]),Aa(e.keyboard,c=>{if(i)return;let l=dn();c.key==="ArrowDown"?(c.preventDefault(),so(Math.min(l+1,r.length-1))):c.key==="ArrowUp"&&(c.preventDefault(),so(Math.max(l-1,0)))},[e.keyboard,i]);let a=(s=fu())!=null?s:0;return z(ft,{children:[r.length>0&&z("h3",{class:ut.heading,children:[z("span",{class:ut.bubble,children:new Intl.NumberFormat("en-US").format(a)})," ","results"]}),z("ol",{class:ut.container,children:r.map((c,l)=>{var m;let u=Ba(t[c.id].title,c.matches.find(({field:h})=>h==="title")),p=Mu((m=t[c.id].path)!=null?m:[],c.matches.find(({field:h})=>h==="path")),d=t[c.id].location;if(qa()){let h=encodeURIComponent(wn()),[v,x]=d.split("#",2);d=`${v}?h=${h.replace(/%20/g,"+")}`,typeof x<"u"&&(d+=`#${x}`)}return z("li",{children:z("a",{ref:h=>{o.current[l]=h},href:d,onClick:()=>Dt(),class:zt(ut.item,{[ut.active]:l===dn()}),children:[z("div",{class:ut.wrapper,children:[z("h2",{class:ut.title,children:u}),z("menu",{class:ut.path,children:p.map(h=>z("li",{children:h}))})]}),z("nav",{class:ut.actions,children:z(co,{children:z(Pu,{})})})]})})})})]})}var Wu={container:"e"};function Vu(e){let t=Ga();return Aa(!0,r=>{var n,o,i,a,s;if(!t)if((r.metaKey||r.ctrlKey)&&r.key==="k")r.preventDefault(),Dt();else if((r.metaKey||r.ctrlKey)&&r.key==="j")document.body.classList.toggle("dark");else if(r.key==="Enter"&&!sr()){r.preventDefault();let c=dn(),l=(o=(n=it.value)==null?void 0:n.items[c])==null?void 0:o.id;if((a=(i=lr.value)==null?void 0:i.items[l])!=null&&a.location){Dt();let u=(s=lr.value)==null?void 0:s.items[l].location;if(qa()){let p=encodeURIComponent(wn()),[d,m]=u.split("#",2);u=`${d}?h=${p.replace(/%20/g,"+")}`,typeof m<"u"&&(u+=`#${m}`)}window.location.href=u}}else r.key==="Escape"&&!sr()&&(r.preventDefault(),Dt())},[t]),z("div",{class:Wu.container,children:[z(ku,{hidden:sr()}),z(Fu,{hidden:sr()})]})}function Ja(e,t){au(e),El(z(Vu,{}),t)}function go(){Dt()}function zu(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function qu(){return R(b(window,"compositionstart").pipe(f(()=>!0)),b(window,"compositionend").pipe(f(()=>!1))).pipe(J(!1))}function Xa(){let e=b(window,"keydown").pipe(f(t=>({mode:sr()?"global":"search",type:t.key,meta:t.ctrlKey||t.metaKey,claim(){t.preventDefault(),t.stopPropagation()}})),L(({mode:t,type:r})=>{if(t==="global"){let n=xt();if(typeof n!="undefined")return!zu(n,r)}return!0}),xe());return qu().pipe(g(t=>t?y:e))}function Ye(){return new URL(location.href)}function dt(e,t=!1){if(X("navigation.instant")&&!t){let r=A("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function Za(){return new I}function Qa(){return location.hash.slice(1)}function es(e){let t=A("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function _o(e){return R(b(window,"hashchange"),e).pipe(f(Qa),J(Qa()),L(t=>t.length>0),se(1))}function ts(e){return _o(e).pipe(f(t=>Le(`[id="${t}"]`)),L(t=>typeof t!="undefined"))}function Ir(e){let t=matchMedia(e);return an(r=>t.addListener(()=>r(t.matches))).pipe(J(t.matches))}function rs(){let e=matchMedia("print");return R(b(window,"beforeprint").pipe(f(()=>!0)),b(window,"afterprint").pipe(f(()=>!1))).pipe(J(e.matches))}function yo(e,t){return e.pipe(g(r=>r?t():y))}function xo(e,t){return new U(r=>{let n=new XMLHttpRequest;return n.open("GET",`${e}`),n.responseType="blob",n.addEventListener("load",()=>{n.status>=200&&n.status<300?(r.next(n.response),r.complete()):r.error(new Error(n.statusText))}),n.addEventListener("error",()=>{r.error(new Error("Network error"))}),n.addEventListener("abort",()=>{r.complete()}),typeof(t==null?void 0:t.progress$)!="undefined"&&(n.addEventListener("progress",o=>{var i;if(o.lengthComputable)t.progress$.next(o.loaded/o.total*100);else{let a=(i=n.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(o.loaded/+a*100)}}),t.progress$.next(5)),n.send(),()=>n.abort()})}function et(e,t){return xo(e,t).pipe(g(r=>r.text()),f(r=>JSON.parse(r)),se(1))}function En(e,t){let r=new DOMParser;return xo(e,t).pipe(g(n=>n.text()),f(n=>r.parseFromString(n,"text/html")),se(1))}function ns(e,t){let r=new DOMParser;return xo(e,t).pipe(g(n=>n.text()),f(n=>r.parseFromString(n,"text/xml")),se(1))}var wo={drawer:G("[data-md-toggle=drawer]"),search:G("[data-md-toggle=search]")};function Eo(e,t){wo[e].checked!==t&&wo[e].click()}function Tn(e){let t=wo[e];return b(t,"change").pipe(f(()=>t.checked),J(t.checked))}function os(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function is(){return R(b(window,"scroll",{passive:!0}),b(window,"resize",{passive:!0})).pipe(f(os),J(os()))}function as(){return{width:innerWidth,height:innerHeight}}function ss(){return b(window,"resize",{passive:!0}).pipe(f(as),J(as()))}function cs(){return re([is(),ss()]).pipe(f(([e,t])=>({offset:e,size:t})),se(1))}function Sn(e,{viewport$:t,header$:r}){let n=t.pipe(fe("size")),o=re([n,r]).pipe(f(()=>wt(e)));return re([r,t,o]).pipe(f(([{height:i},{offset:a,size:s},{x:c,y:l}])=>({offset:{x:a.x-c,y:a.y-l+i},size:s})))}var Ku=G("#__config"),mr=JSON.parse(Ku.textContent);mr.base=`${new URL(mr.base,Ye())}`;function Ue(){return mr}function X(e){return mr.features.includes(e)}function Bt(e,t){return typeof t!="undefined"?mr.translations[e].replace("#",t.toString()):mr.translations[e]}function ht(e,t=document){return G(`[data-md-component=${e}]`,t)}function Ee(e,t=document){return P(`[data-md-component=${e}]`,t)}function Bu(e){let t=G(".md-typeset > :first-child",e);return b(t,"click",{once:!0}).pipe(f(()=>G(".md-typeset",e)),f(r=>({hash:__md_hash(r.innerHTML)})))}function ls(e){if(!X("announce.dismiss")||!e.childElementCount)return y;if(!e.hidden){let t=G(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return j(()=>{let t=new I;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),Bu(e).pipe($(r=>t.next(r)),V(()=>t.complete()),f(r=>H({ref:e},r)))})}function Yu(e,{target$:t}){return t.pipe(f(r=>({hidden:r!==e})))}function us(e,t){let r=new I;return r.subscribe(({hidden:n})=>{e.hidden=n}),Yu(e,t).pipe($(n=>r.next(n)),V(()=>r.complete()),f(n=>H({ref:e},n)))}function To(e,t){return t==="inline"?A("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},A("div",{class:"md-tooltip__inner md-typeset"})):A("div",{class:"md-tooltip",id:e,role:"tooltip"},A("div",{class:"md-tooltip__inner md-typeset"}))}function On(...e){return A("div",{class:"md-tooltip2",role:"dialog"},A("div",{class:"md-tooltip2__inner md-typeset"},e))}function ps(...e){return A("div",{class:"md-tooltip2",role:"tooltip"},A("div",{class:"md-tooltip2__inner md-typeset"},e))}function fs(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return A("aside",{class:"md-annotation",tabIndex:0},To(t),A("a",{href:r,class:"md-annotation__index",tabIndex:-1},A("span",{"data-md-annotation-id":e})))}else return A("aside",{class:"md-annotation",tabIndex:0},To(t),A("span",{class:"md-annotation__index",tabIndex:-1},A("span",{"data-md-annotation-id":e})))}function ms(e){return A("button",{class:"md-code__button",title:Bt("clipboard.copy"),"data-clipboard-target":`#${e} > code`,"data-md-type":"copy"})}function ds(){return A("button",{class:"md-code__button",title:"Toggle line selection","data-md-type":"select"})}function hs(){return A("nav",{class:"md-code__nav"})}var Xu=_r(So());function bs(e){return A("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>A("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?Li(r):r)))}function Oo(e){let t=`tabbed-control tabbed-control--${e}`;return A("div",{class:t,hidden:!0},A("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function gs(e){return A("div",{class:"md-typeset__scrollwrap"},A("div",{class:"md-typeset__table"},e))}function Zu(e){var n;let t=Ue(),r=new URL(`../${e.version}/`,t.base);return A("li",{class:"md-version__item"},A("a",{href:`${r}`,class:"md-version__link"},e.title,((n=t.version)==null?void 0:n.alias)&&e.aliases.length>0&&A("span",{class:"md-version__alias"},e.aliases[0])))}function _s(e,t){var n;let r=Ue();return e=e.filter(o=>{var i;return!((i=o.properties)!=null&&i.hidden)}),A("div",{class:"md-version"},A("button",{class:"md-version__current","aria-label":Bt("select.version")},t.title,((n=r.version)==null?void 0:n.alias)&&t.aliases.length>0&&A("span",{class:"md-version__alias"},t.aliases[0])),A("ul",{class:"md-version__list"},e.map(Zu)))}var Qu=0;function ep(e,t=250){let r=re([ir(e),Ft(e,t)]).pipe(f(([o,i])=>o||i),ie()),n=j(()=>Ai(e)).pipe(oe(Ut),Lr(1),Ze(r),f(()=>Ci(e)));return r.pipe(Sr(o=>o),g(()=>re([r,n])),f(([o,i])=>({active:o,offset:i})),xe())}function Rr(e,t,r=250){let{content$:n,viewport$:o}=t,i=`__tooltip2_${Qu++}`;return j(()=>{let a=new I,s=new Un(!1);a.pipe(he(),ye(!1)).subscribe(s);let c=s.pipe(Tr(u=>Ve(+!u*250,Wn)),ie(),g(u=>u?n:y),$(u=>u.id=i),xe());re([a.pipe(f(({active:u})=>u)),c.pipe(g(u=>Ft(u,250)),J(!1))]).pipe(f(u=>u.some(p=>p))).subscribe(s);let l=s.pipe(L(u=>u),pe(c,o),f(([u,p,{size:d}])=>{let m=e.getBoundingClientRect(),h=m.width/2;if(p.role==="tooltip")return{x:h,y:8+m.height};if(m.y>=d.height/2){let{height:v}=Ae(p);return{x:h,y:-16-v}}else return{x:h,y:16+m.height}}));return re([c,a,l]).subscribe(([u,{offset:p},d])=>{u.style.setProperty("--md-tooltip-host-x",`${p.x}px`),u.style.setProperty("--md-tooltip-host-y",`${p.y}px`),u.style.setProperty("--md-tooltip-x",`${d.x}px`),u.style.setProperty("--md-tooltip-y",`${d.y}px`),u.classList.toggle("md-tooltip2--top",d.y<0),u.classList.toggle("md-tooltip2--bottom",d.y>=0)}),s.pipe(L(u=>u),pe(c,(u,p)=>p),L(u=>u.role==="tooltip")).subscribe(u=>{let p=Ae(G(":scope > *",u));u.style.setProperty("--md-tooltip-width",`${p.width}px`),u.style.setProperty("--md-tooltip-tail","0px")}),s.pipe(ie(),Ie(je),pe(c)).subscribe(([u,p])=>{p.classList.toggle("md-tooltip2--active",u)}),re([s.pipe(L(u=>u)),c]).subscribe(([u,p])=>{p.role==="dialog"?(e.setAttribute("aria-controls",i),e.setAttribute("aria-haspopup","dialog")):e.setAttribute("aria-describedby",i)}),s.pipe(L(u=>!u)).subscribe(()=>{e.removeAttribute("aria-controls"),e.removeAttribute("aria-describedby"),e.removeAttribute("aria-haspopup")}),ep(e,r).pipe($(u=>a.next(u)),V(()=>a.complete()),f(u=>H({ref:e},u)))})}function Ge(e,{viewport$:t},r=document.body){return Rr(e,{content$:new U(n=>{let o=e.title,i=ps(o);return n.next(i),e.removeAttribute("title"),r.append(i),()=>{i.remove(),e.setAttribute("title",o)}}),viewport$:t},0)}function tp(e,t){let r=j(()=>re([Hi(e),Ut(t)])).pipe(f(([{x:n,y:o},i])=>{let{width:a,height:s}=Ae(e);return{x:n-i.x+a/2,y:o-i.y+s/2}}));return ir(e).pipe(g(n=>r.pipe(f(o=>({active:n,offset:o})),Me(+!n||1/0))))}function ys(e,t,{target$:r}){let[n,o]=Array.from(e.children);return j(()=>{let i=new I,a=i.pipe(he(),ye(!0));return i.subscribe({next({offset:s}){e.style.setProperty("--md-tooltip-x",`${s.x}px`),e.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),Et(e).pipe(Q(a)).subscribe(s=>{e.toggleAttribute("data-md-visible",s)}),R(i.pipe(L(({active:s})=>s)),i.pipe(Be(250),L(({active:s})=>!s))).subscribe({next({active:s}){s?e.prepend(n):n.remove()},complete(){e.prepend(n)}}),i.pipe(Xe(16,je)).subscribe(({active:s})=>{n.classList.toggle("md-tooltip--active",s)}),i.pipe(Lr(125,je),L(()=>!!e.offsetParent),f(()=>e.offsetParent.getBoundingClientRect()),f(({x:s})=>s)).subscribe({next(s){s?e.style.setProperty("--md-tooltip-0",`${-s}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),b(o,"click").pipe(Q(a),L(s=>!(s.metaKey||s.ctrlKey))).subscribe(s=>{s.stopPropagation(),s.preventDefault()}),b(o,"mousedown").pipe(Q(a),pe(i)).subscribe(([s,{active:c}])=>{var l;if(s.button!==0||s.metaKey||s.ctrlKey)s.preventDefault();else if(c){s.preventDefault();let u=e.parentElement.closest(".md-annotation");u instanceof HTMLElement?u.focus():(l=xt())==null||l.blur()}}),r.pipe(Q(a),L(s=>s===n),It(125)).subscribe(()=>e.focus()),tp(e,t).pipe($(s=>i.next(s)),V(()=>i.complete()),f(s=>H({ref:e},s)))})}function rp(e){let t=Ue();if(e.tagName!=="CODE")return[e];let r=[".c",".c1",".cm"];if(t.annotate){let n=e.closest("[class|=language]");if(n)for(let o of Array.from(n.classList)){if(!o.startsWith("language-"))continue;let[,i]=o.split("-");i in t.annotate&&r.push(...t.annotate[i])}}return P(r.join(", "),e)}function np(e){let t=[];for(let r of rp(e)){let n=[],o=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=o.nextNode();i;i=o.nextNode())n.push(i);for(let i of n){let a;for(;a=/(\(\d+\))(!)?/.exec(i.textContent);){let[,s,c]=a;if(typeof c=="undefined"){let l=i.splitText(a.index);i=l.splitText(s.length),t.push(l)}else{i.textContent=s,t.push(i);break}}}}return t}function xs(e,t){t.append(...Array.from(e.childNodes))}function Ln(e,t,{target$:r,print$:n}){let o=t.closest("[id]"),i=o==null?void 0:o.id,a=new Map;for(let s of np(t)){let[,c]=s.textContent.match(/\((\d+)\)/);Le(`:scope > li:nth-child(${c})`,e)&&(a.set(c,fs(c,i)),s.replaceWith(a.get(c)))}return a.size===0?y:j(()=>{let s=new I,c=s.pipe(he(),ye(!0)),l=[];for(let[u,p]of a)l.push([G(".md-typeset",p),G(`:scope > li:nth-child(${u})`,e)]);return n.pipe(Q(c)).subscribe(u=>{e.hidden=!u,e.classList.toggle("md-annotation-list",u);for(let[p,d]of l)u?xs(p,d):xs(d,p)}),R(...[...a].map(([,u])=>ys(u,t,{target$:r}))).pipe(V(()=>s.complete()),xe())})}function ws(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return ws(t)}}function Es(e,t){return j(()=>{let r=ws(e);return typeof r!="undefined"?Ln(r,e,t):y})}var Ss=_r(Mo());var op=0,Ts=R(b(window,"keydown").pipe(f(()=>!0)),R(b(window,"keyup"),b(window,"contextmenu")).pipe(f(()=>!1))).pipe(J(!1),se(1));function Os(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return Os(t)}}function ip(e){return Re(e).pipe(f(({width:t})=>({scrollable:Mr(e).width>t})),fe("scrollable"))}function Ls(e,t){let{matches:r}=matchMedia("(hover)"),n=j(()=>{let o=new I,i=o.pipe(Gn(1));o.subscribe(({scrollable:m})=>{m&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let a=[],s=e.closest("pre"),c=s.closest("[id]"),l=c?c.id:op++;s.id=`__code_${l}`;let u=[],p=e.closest(".highlight");if(p instanceof HTMLElement){let m=Os(p);if(typeof m!="undefined"&&(p.classList.contains("annotate")||X("content.code.annotate"))){let h=Ln(m,e,t);u.push(Re(p).pipe(Q(i),f(({width:v,height:x})=>v&&x),ie(),g(v=>v?h:y)))}}let d=P(":scope > span[id]",e);if(d.length&&(e.classList.add("md-code__content"),e.closest(".select")||X("content.code.select")&&!e.closest(".no-select"))){let m=+d[0].id.split("-").pop(),h=ds();a.push(h),X("content.tooltips")&&u.push(Ge(h,{viewport$}));let v=b(h,"click").pipe(Or(M=>!M,!1),$(()=>h.blur()),xe());v.subscribe(M=>{h.classList.toggle("md-code__button--active",M)});let x=me(d).pipe(oe(M=>Ft(M).pipe(f(O=>[M,O]))));v.pipe(g(M=>M?x:y)).subscribe(([M,O])=>{let N=Le(".hll.select",M);if(N&&!O)N.replaceWith(...Array.from(N.childNodes));else if(!N&&O){let ee=document.createElement("span");ee.className="hll select",ee.append(...Array.from(M.childNodes).slice(1)),M.append(ee)}});let w=me(d).pipe(oe(M=>b(M,"mousedown").pipe($(O=>O.preventDefault()),f(()=>M)))),E=v.pipe(g(M=>M?w:y),pe(Ts),f(([M,O])=>{var ee;let N=d.indexOf(M)+m;if(O===!1)return[N,N];{let le=P(".hll",e).map(ce=>d.indexOf(ce.parentElement)+m);return(ee=window.getSelection())==null||ee.removeAllRanges(),[Math.min(N,...le),Math.max(N,...le)]}})),_=_o(y).pipe(L(M=>M.startsWith(`__codelineno-${l}-`)));_.subscribe(M=>{let[,,O]=M.split("-"),N=O.split(":").map(le=>+le-m+1);N.length===1&&N.push(N[0]);for(let le of P(".hll:not(.select)",e))le.replaceWith(...Array.from(le.childNodes));let ee=d.slice(N[0]-1,N[1]);for(let le of ee){let ce=document.createElement("span");ce.className="hll",ce.append(...Array.from(le.childNodes).slice(1)),le.append(ce)}}),_.pipe(Me(1),Ie(ge)).subscribe(M=>{if(M.includes(":")){let O=document.getElementById(M.split(":")[0]);O&&setTimeout(()=>{let N=O,ee=-64;for(;N!==document.body;)ee+=N.offsetTop,N=N.offsetParent;window.scrollTo({top:ee})},1)}});let be=me(P('a[href^="#__codelineno"]',p)).pipe(oe(M=>b(M,"click").pipe($(O=>O.preventDefault()),f(()=>M)))).pipe(Q(i),pe(Ts),f(([M,O])=>{let ee=+G(`[id="${M.hash.slice(1)}"]`).parentElement.id.split("-").pop();if(O===!1)return[ee,ee];{let le=P(".hll",e).map(ce=>+ce.parentElement.id.split("-").pop());return[Math.min(ee,...le),Math.max(ee,...le)]}}));R(E,be).subscribe(M=>{let O=`#__codelineno-${l}-`;M[0]===M[1]?O+=M[0]:O+=`${M[0]}:${M[1]}`,history.replaceState({},"",O),window.dispatchEvent(new HashChangeEvent("hashchange",{newURL:window.location.origin+window.location.pathname+O,oldURL:window.location.href}))})}if(Ss.default.isSupported()&&(e.closest(".copy")||X("content.code.copy")&&!e.closest(".no-copy"))){let m=ms(s.id);a.push(m),X("content.tooltips")&&u.push(Ge(m,{viewport$}))}if(a.length){let m=hs();m.append(...a),s.insertBefore(m,e)}return ip(e).pipe($(m=>o.next(m)),V(()=>o.complete()),f(m=>H({ref:e},m)),Rt(R(...u).pipe(Q(i))))});return X("content.lazy")?Et(e).pipe(L(o=>o),Me(1),g(()=>n)):n}function ap(e,{target$:t,print$:r}){let n=!0;return R(t.pipe(f(o=>o.closest("details:not([open])")),L(o=>e===o),f(()=>({action:"open",reveal:!0}))),r.pipe(L(o=>o||!n),$(()=>n=e.open),f(o=>({action:o?"open":"close"}))))}function Ms(e,t){return j(()=>{let r=new I;return r.subscribe(({action:n,reveal:o})=>{e.toggleAttribute("open",n==="open"),o&&e.scrollIntoView()}),ap(e,t).pipe($(n=>r.next(n)),V(()=>r.complete()),f(n=>H({ref:e},n)))})}var ks=0,As=new Map;function sp(e){let t=document.createElement("h3");t.innerHTML=e.innerHTML;let r=[t],n=e.nextElementSibling;for(;n&&!(n instanceof HTMLHeadingElement);)r.push(n.cloneNode(!0)),n=n.nextElementSibling;return r}function cp(e,t){for(let r of P("[href], [src]",e))for(let n of["href","src"]){let o=r.getAttribute(n);if(o&&!/^(?:[a-z]+:)?\/\//i.test(o)){r[n]=new URL(r.getAttribute(n),t).toString();break}}for(let r of P("[name^=__], [for]",e))for(let n of["id","for","name"]){let o=r.getAttribute(n);o&&r.setAttribute(n,`${o}$preview_${ks}`)}return ks++,Y(e)}function lp(e){let t=As.get(e.toString());return t?Y(t):En(e).pipe(g(r=>cp(r,e)),f(r=>(As.set(e.toString(),r),r)))}function Cs(e,t){let{sitemap$:r}=t;if(!(e instanceof HTMLAnchorElement))return y;if(!(X("navigation.instant.preview")||e.hasAttribute("data-preview")))return y;e.removeAttribute("title");let n=re([ir(e),Ft(e).pipe(ke(1))]).pipe(f(([i,a])=>i||a),ie(),L(i=>i));return $t([r,n]).pipe(g(([i])=>{let a=new URL(e.href);return a.search=a.hash="",i.has(`${a}`)?Y(a):y}),g(i=>lp(i)),g(i=>{let a=e.hash?`article [id="${decodeURIComponent(e.hash.slice(1))}"]`:"article h1",s=Le(a,i);return typeof s=="undefined"?y:Y(sp(s))})).pipe(g(i=>{let a=new U(s=>{let c=On(...i);return s.next(c),document.body.append(c),()=>c.remove()});return Rr(e,H({content$:a},t))}))}var Hs=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.flowchartTitleText{fill:var(--md-mermaid-label-fg-color)}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel p,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel p{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color)}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}.classDiagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs marker.marker.composition.class path,defs marker.marker.dependency.class path,defs marker.marker.extension.class path{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs marker.marker.aggregation.class path{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}.statediagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel,.nodeLabel p{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}a .nodeLabel{text-decoration:underline}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}[id^=entity] path,[id^=entity] rect{fill:var(--md-default-bg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs .marker.oneOrMore.er *,defs .marker.onlyOne.er *,defs .marker.zeroOrMore.er *,defs .marker.zeroOrOne.er *{stroke:var(--md-mermaid-edge-color)!important}text:not([class]):last-child{fill:var(--md-mermaid-label-fg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var ko,pp=0;function fp(){return typeof mermaid=="undefined"||mermaid instanceof Element?ar("https://unpkg.com/mermaid@11/dist/mermaid.min.js"):Y(void 0)}function $s(e){return e.classList.remove("mermaid"),ko||(ko=fp().pipe($(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Hs,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),f(()=>{}),se(1))),ko.subscribe(()=>Uo(null,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${pp++}`,r=A("div",{class:"mermaid"}),n=e.textContent,{svg:o,fn:i}=yield mermaid.render(t,n),a=r.attachShadow({mode:"closed"});a.innerHTML=o,e.replaceWith(r),i==null||i(a)})),ko.pipe(f(()=>({ref:e})))}var Ps=A("table");function Is(e){return e.replaceWith(Ps),Ps.replaceWith(gs(e)),Y({ref:e})}function mp(e){let t=e.find(r=>r.checked)||e[0];return R(...e.map(r=>b(r,"change").pipe(f(()=>G(`label[for="${r.id}"]`))))).pipe(J(G(`label[for="${t.id}"]`)),f(r=>({active:r})))}function Rs(e,{viewport$:t,target$:r}){let n=G(".tabbed-labels",e),o=P(":scope > input",e),i=Oo("prev");e.append(i);let a=Oo("next");return e.append(a),j(()=>{let s=new I,c=s.pipe(he(),ye(!0));re([s,Re(e),Et(e)]).pipe(Q(c),Xe(1,je)).subscribe({next([{active:l},u]){let p=wt(l),{width:d}=Ae(l);e.style.setProperty("--md-indicator-x",`${p.x}px`),e.style.setProperty("--md-indicator-width",`${d}px`);let m=ln(n);(p.xm.x+u.width)&&n.scrollTo({left:Math.max(0,p.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),re([Ut(n),Re(n)]).pipe(Q(c)).subscribe(([l,u])=>{let p=Mr(n);i.hidden=l.x<16,a.hidden=l.x>p.width-u.width-16}),R(b(i,"click").pipe(f(()=>-1)),b(a,"click").pipe(f(()=>1))).pipe(Q(c)).subscribe(l=>{let{width:u}=Ae(n);n.scrollBy({left:u*l,behavior:"smooth"})}),r.pipe(Q(c),L(l=>o.includes(l))).subscribe(l=>l.click()),n.classList.add("tabbed-labels--linked");for(let l of o){let u=G(`label[for="${l.id}"]`);u.replaceChildren(A("a",{href:`#${u.htmlFor}`,tabIndex:-1},...Array.from(u.childNodes))),b(u.firstElementChild,"click").pipe(Q(c),L(p=>!(p.metaKey||p.ctrlKey)),$(p=>{p.preventDefault(),p.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${u.htmlFor}`),u.click()})}return X("content.tabs.link")&&s.pipe(ke(1),pe(t)).subscribe(([{active:l},{offset:u}])=>{let p=l.innerText.trim();if(l.hasAttribute("data-md-switching"))l.removeAttribute("data-md-switching");else{let d=e.offsetTop-u.y;for(let h of P("[data-tabs]"))for(let v of P(":scope > input",h)){let x=G(`label[for="${v.id}"]`);if(x!==l&&x.innerText.trim()===p){x.setAttribute("data-md-switching",""),v.click();break}}window.scrollTo({top:e.offsetTop-d});let m=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([p,...m])])}}),s.pipe(Q(c)).subscribe(()=>{for(let l of P("audio, video",e))l.offsetWidth&&l.autoplay?l.play().catch(()=>{}):l.pause()}),mp(o).pipe($(l=>s.next(l)),V(()=>s.complete()),f(l=>H({ref:e},l)))}).pipe(Ht(ge))}function js(e,t){let{viewport$:r,target$:n,print$:o}=t;return R(...P(".annotate:not(.highlight)",e).map(i=>Es(i,{target$:n,print$:o})),...P("pre:not(.mermaid) > code",e).map(i=>Ls(i,{target$:n,print$:o})),...P("a",e).map(i=>Cs(i,t)),...P("pre.mermaid",e).map(i=>$s(i)),...P("table:not([class])",e).map(i=>Is(i)),...P("details",e).map(i=>Ms(i,{target$:n,print$:o})),...P("[data-tabs]",e).map(i=>Rs(i,{viewport$:r,target$:n})),...P("[title]:not([data-preview])",e).filter(()=>X("content.tooltips")).map(i=>Ge(i,{viewport$:r})),...P(".footnote-ref",e).filter(()=>X("content.footnote.tooltips")).map(i=>Rr(i,{content$:new U(a=>{let s=new URL(i.href).hash.slice(1),c=Array.from(document.getElementById(s).cloneNode(!0).children),l=On(...c);return a.next(l),document.body.append(l),()=>l.remove()}),viewport$:r})))}function dp(e,{alert$:t}){return t.pipe(g(r=>R(Y(!0),Y(!1).pipe(It(2e3))).pipe(f(n=>({message:r,active:n})))))}function Fs(e,t){let r=G(".md-typeset",e);return j(()=>{let n=new I;return n.subscribe(({message:o,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=o}),dp(e,t).pipe($(o=>n.next(o)),V(()=>n.complete()),f(o=>H({ref:e},o)))})}function hp({viewport$:e}){if(!X("header.autohide"))return Y(!1);let t=e.pipe(f(({offset:{y:o}})=>o),Pt(2,1),f(([o,i])=>[oMath.abs(i-o.y)>100),f(([,[o]])=>o),ie()),n=Tn("search");return re([e,n]).pipe(f(([{offset:o},i])=>o.y>400&&!i),ie(),g(o=>o?r:Y(!1)),J(!1))}function Us(e,t){return j(()=>re([Re(e),hp(t)])).pipe(f(([{height:r},n])=>({height:r,hidden:n})),ie((r,n)=>r.height===n.height&&r.hidden===n.hidden),se(1))}function Ns(e,{viewport$:t,header$:r,main$:n}){return j(()=>{let o=new I,i=o.pipe(he(),ye(!0));o.pipe(fe("active"),Ze(r)).subscribe(([{active:s},{hidden:c}])=>{e.classList.toggle("md-header--shadow",s&&!c),e.hidden=c});let a=me(P("[title]",e)).pipe(L(()=>X("content.tooltips")),oe(s=>Ge(s,{viewport$:t})));return n.subscribe(o),r.pipe(Q(i),f(s=>H({ref:e},s)),Rt(a.pipe(Q(i))))})}function vp(e,{viewport$:t,header$:r}){return Sn(e,{viewport$:t,header$:r}).pipe(f(({offset:{y:n}})=>{let{height:o}=Ae(e);return{active:o>0&&n>=o}}),fe("active"))}function Ds(e,t){return j(()=>{let r=new I;r.subscribe({next({active:o}){e.classList.toggle("md-header__title--active",o)},complete(){e.classList.remove("md-header__title--active")}});let n=Le(".md-content h1");return typeof n=="undefined"?y:vp(n,t).pipe($(o=>r.next(o)),V(()=>r.complete()),f(o=>H({ref:e},o)))})}function Ws(e,{viewport$:t,header$:r}){let n=r.pipe(f(({height:i})=>i),ie()),o=n.pipe(g(()=>Re(e).pipe(f(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),fe("bottom"))));return re([n,o,t]).pipe(f(([i,{top:a,bottom:s},{offset:{y:c},size:{height:l}}])=>(l=Math.max(0,l-Math.max(0,a-c,i)-Math.max(0,l+c-s)),{offset:a-i,height:l,active:a-i<=c})),ie((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function bp(e){let t=__md_get("__palette")||{index:e.findIndex(n=>matchMedia(n.getAttribute("data-md-color-media")).matches)},r=Math.max(0,Math.min(t.index,e.length-1));return Y(...e).pipe(oe(n=>b(n,"change").pipe(f(()=>n))),J(e[r]),f(n=>({index:e.indexOf(n),color:{media:n.getAttribute("data-md-color-media"),scheme:n.getAttribute("data-md-color-scheme"),primary:n.getAttribute("data-md-color-primary"),accent:n.getAttribute("data-md-color-accent")}})),se(1))}function Vs(e){let t=P("input",e),r=A("meta",{name:"theme-color"});document.head.appendChild(r);let n=A("meta",{name:"color-scheme"});document.head.appendChild(n);let o=Ir("(prefers-color-scheme: light)");return j(()=>{let i=new I;return i.subscribe(a=>{if(document.body.setAttribute("data-md-color-switching",""),a.color.media==="(prefers-color-scheme)"){let s=matchMedia("(prefers-color-scheme: light)"),c=document.querySelector(s.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");a.color.scheme=c.getAttribute("data-md-color-scheme"),a.color.primary=c.getAttribute("data-md-color-primary"),a.color.accent=c.getAttribute("data-md-color-accent")}for(let[s,c]of Object.entries(a.color))document.body.setAttribute(`data-md-color-${s}`,c);for(let s=0;sa.key==="Enter"),pe(i,(a,s)=>s)).subscribe(({index:a})=>{a=(a+1)%t.length,t[a].click(),t[a].focus()}),i.pipe(f(()=>{let a=ht("header"),s=window.getComputedStyle(a);return n.content=s.colorScheme,s.backgroundColor.match(/\d+/g).map(c=>(+c).toString(16).padStart(2,"0")).join("")})).subscribe(a=>r.content=`#${a}`),i.pipe(Ie(ge)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),bp(t).pipe(Q(o.pipe(ke(1))),jt(),$(a=>i.next(a)),V(()=>i.complete()),f(a=>H({ref:e},a)))})}function zs(e,{progress$:t}){return j(()=>{let r=new I;return r.subscribe(({value:n})=>{e.style.setProperty("--md-progress-value",`${n}`)}),t.pipe($(n=>r.next({value:n})),V(()=>r.complete()),f(n=>({ref:e,value:n})))})}var qs='.v u{text-decoration:underline!important;text-decoration-style:wavy!important;text-decoration-thickness:1px!important}.p{-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);background-color:rgba(var(--color-backdrop)/var(--alpha-lighter));cursor:pointer;height:100%;pointer-events:auto;position:absolute;transition:opacity .25s;width:100%}.p.m{opacity:0;pointer-events:none;transition:opacity .35s}.r{align-items:center;background-color:initial;border:none;border-radius:var(--space-2);cursor:pointer;display:flex;flex-shrink:0;font-family:var(--font-family);height:36px;justify-content:center;outline:none;padding:0;position:relative;transition:background-color .25s,color .25s;width:36px;z-index:1}.r svg{stroke:rgb(var(--color-foreground));height:18px;opacity:.5;width:18px}.r:before{background-color:rgb(var(--color-background-subtle));border-radius:var(--border-radius-2);content:"";inset:0;opacity:0;position:absolute;transform:scale(.75);transition:transform 125ms,opacity 125ms;z-index:0}.r:hover:before{opacity:1;transform:scale(1)}.r.c{cursor:auto}.r.c:before{display:none}.n{-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);background-color:rgba(var(--color-background)/var(--alpha-light));border-radius:var(--space-3);box-shadow:0 0 60px #0000000d;display:flex;height:480px;overflow:hidden;pointer-events:auto;position:absolute;transition:transform .25s cubic-bezier(.16,1,.3,1),opacity .25s;width:640px}.n.l{opacity:0;pointer-events:none;transform:scale(1.1);transition:transform .25s .15s,opacity .15s}@media (max-width:680px){.n{border-radius:0;height:100%;width:100%}}.u{display:flex;flex-basis:min-content;flex-direction:column;flex-grow:1;flex-shrink:0}@keyframes d{0%{transform:scale(0)}50%{transform:scale(1.2)}to{transform:scale(1)}}.y{animation:d .25s ease-in-out;background:var(--color-highlight);border-radius:100%;color:#fff;font-size:8px;font-weight:700;height:12px;padding-top:1px;position:absolute;right:4px;top:4px;width:12px}.i{background-color:rgb(var(--color-background-subtle)/var(--alpha-lighter));flex-shrink:0;overflow:scroll;position:relative;transition:width .35s cubic-bezier(.16,1,.3,1),opacity .25s;width:200px}.i>*{transform:translate(0);transition:transform .25s cubic-bezier(.16,1,.3,1)}.i.l{opacity:0;width:0}.i.l>*{transform:translate(-48px)}@media (max-width:680px){.i{-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);background-color:rgba(var(--color-background-subtle)/var(--alpha-light));box-shadow:0 0 60px #00000026;height:100%;position:absolute;right:0;top:0}}.w{border-bottom:1px solid rgb(var(--color-foreground)/var(--alpha-lightest));display:flex;gap:var(--space-1);padding:var(--space-2)}.k{-webkit-overflow-scrolling:touch;overflow:auto;overscroll-behavior:contain}.z{padding:8px 10px}.X{color:rgb(var(--color-foreground)/var(--alpha-light));padding:var(--space-2);position:absolute;width:200px}.X,.j{display:flex;flex-direction:column}.j{gap:2px;list-style:none;padding:0}.F,.j{margin:0}.F{font-size:16px;font-weight:400}.F,.I{padding:8px}.I{font-size:14px;margin:4px 0 0;opacity:.5}.I,.o{font-size:12px}.o{cursor:pointer;display:flex;padding:4px 8px;position:relative}.o:before{background-color:var(--color-highlight-transparent);border-radius:var(--space-1);content:"";inset:0;opacity:0;position:absolute;transform:scale(.75);transition:transform 125ms,opacity 125ms;z-index:0}.o.g:before,.o:hover:before{opacity:1;transform:scale(1)}.o.g,.o:hover{color:var(--color-highlight)}.R{flex-grow:1}.R,.q{position:relative}.q{font-weight:700}.f{flex-grow:1}.f input{background:#0000;border:none;color:rgb(var(--color-foreground));font-family:var(--font-family);font-size:16px;height:100%;letter-spacing:-.25px;outline:none;width:100%}.b{color:rgb(var(--color-foreground)/var(--alpha-light));display:flex;flex-direction:column;gap:2px;line-height:1.3;list-style:none;margin:var(--space-2);margin-top:0;padding:0}.A,.b li{margin:0}.A{color:rgb(var(--color-foreground)/var(--alpha-lighter));font-size:12px;margin-top:var(--space-2);padding:0 18px}.a{border-radius:var(--space-2);color:inherit;cursor:pointer;display:flex;flex-direction:row;flex-grow:1;padding:8px 10px;position:relative;text-decoration:none}.a:before{background-color:rgb(var(--color-background-subtle));border-radius:var(--border-radius-2);content:"";display:block;inset:0;opacity:0;position:absolute;transform:scale(.9);transition:transform 125ms,opacity 125ms;z-index:0}@media (pointer:fine){.a.h:before,.a:hover:before{opacity:1;transform:scale(1)}}.a mark{background:#0000;color:var(--color-highlight)}.a u{background-color:var(--color-highlight-transparent);border-radius:2px;box-shadow:0 0 0 1px var(--color-highlight-transparent);text-decoration:none}.B{flex-grow:1}.s{margin-right:-8px;opacity:0;position:relative;transform:translate(-2px);transition:transform 125ms,opacity 125ms;z-index:0}@media (pointer:fine){.h>.s,:hover>.s{opacity:1;transform:none}}.x{font-size:14px;margin:0;position:relative}.x code{background:rgb(var(--color-background-subtle));border-radius:var(--space-1);font-size:13px;padding:2px 4px}.t{color:rgb(var(--color-foreground)/var(--alpha-lighter));display:inline-flex;flex-wrap:wrap;font-size:12px;gap:var(--space-1);list-style:none;margin:0;padding:0;position:relative}.t li{white-space:nowrap}.t li:after{content:"/";display:inline;margin-left:var(--space-1)}.t li:last-child:after{content:"";display:none}.e{--space-1:4px;--space-2:calc(var(--space-1)*2);--space-3:calc(var(--space-2)*2);--space-4:calc(var(--space-3)*2);--space-5:calc(var(--space-4)*2);--alpha-light:.7;--alpha-lighter:.54;--alpha-lightest:.1;--color-highlight:var(--md-accent-fg-color,#526cfe);--color-highlight-transparent:var(--md-accent-fg-color--transparent,#526cfe1a);--border-radius-1:var(--space-1);--border-radius-2:var(--space-2);--border-radius-3:calc(var(--space-1) + var(--space-2));--font-family:var(--md-text-font-family,Inter,Roboto Flex,system-ui,sans-serif);--font-size:16px;--line-height:1.5;--letter-spacing:-.5px;-webkit-font-smoothing:antialiased;align-items:center;display:flex;font-family:var(--font-family);font-size:var(--font-size);height:100vh;justify-content:center;letter-spacing:var(--letter-spacing);line-height:var(--line-height);pointer-events:none;position:absolute;width:100vw}@media (pointer:coarse){.e{height:-webkit-fill-available}}.e *,.e :after,.e :before{box-sizing:border-box}';function Ks(e,{index$:t}){let r=Ue(),n=document.createElement("div");document.body.appendChild(n),n.style.position="fixed",n.style.height="100%",n.style.top="0",n.style.zIndex="4";let o=n.attachShadow({mode:"open"});o.appendChild(A("style",{},qs.toString()));try{Ya(r.search,{highlight:r.features.includes("search.highlight")}),me(t).subscribe(i=>{for(let a of i.items)a.location=new URL(a.location,r.base).toString();Ja(i,o)}),b(e,"click").subscribe(()=>{go()}),Tn("search").pipe(ke(1)).subscribe(()=>go())}catch(i){e.hidden=!0;let a=G("label[for=__search]");a.hidden=!0}return Ke}var Bs=_r(So());function Ys(e,{index$:t,location$:r}){return re([t,r.pipe(J(Ye()),L(n=>!!n.searchParams.get("h")))]).pipe(f(([n,o])=>_p(n.config)(o.searchParams.get("h"))),f(n=>{var a;let o=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let s=i.nextNode();s;s=i.nextNode())if((a=s.parentElement)!=null&&a.offsetHeight){let c=s.textContent,l=n(c);l.length>c.length&&o.set(s,l)}for(let[s,c]of o){let{childNodes:l}=A("span",null,c);s.replaceWith(...Array.from(l))}return{ref:e,nodes:o}}))}function _p(e){let t=e.separator.split("|").map(o=>o.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":o).join("|"),r=new RegExp(t,"img"),n=(o,i,a)=>`${i}${a}`;return o=>{o=o.replace(/\s+/g," ").replace(/&/g,"&").trim();let i=new RegExp(`(^|${e.separator}|)(${o.split(r).map(a=>a.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&")).filter(a=>a.length>0).join("|")})`,"img");return a=>(0,Bs.default)(a).replace(i,n).replace(/<\/mark>(\s+)]*>/img,"$1")}}function yp(e,{viewport$:t,main$:r}){let n=e.closest(".md-grid"),o=n.offsetTop-n.parentElement.offsetTop;return re([r,t]).pipe(f(([{offset:i,height:a},{offset:{y:s}}])=>(a=a+Math.min(o,Math.max(0,s-i))-o,{height:a,locked:s>=i+o})),ie((i,a)=>i.height===a.height&&i.locked===a.locked))}function Ao(e,n){var o=n,{header$:t}=o,r=gr(o,["header$"]);let i=G(".md-sidebar__scrollwrap",e),{y:a}=wt(i);return j(()=>{let s=new I,c=s.pipe(he(),ye(!0)),l=s.pipe(Xe(0,je));return l.pipe(pe(t)).subscribe({next([{height:u},{height:p}]){i.style.height=`${u-2*a}px`,e.style.top=`${p}px`},complete(){i.style.height="",e.style.top=""}}),l.pipe(Sr()).subscribe(()=>{for(let u of P(".md-nav__link--active[href]",e)){if(!u.clientHeight)continue;let p=u.closest(".md-sidebar__scrollwrap");if(typeof p!="undefined"){let d=u.offsetTop-p.offsetTop,{height:m}=Ae(p);p.scrollTo({top:d-m/2})}}}),me(P("label[tabindex]",e)).pipe(oe(u=>b(u,"click").pipe(Ie(ge),f(()=>u),Q(c)))).subscribe(u=>{let p=G(`[id="${u.htmlFor}"]`);G(`[aria-labelledby="${u.id}"]`).setAttribute("aria-expanded",`${p.checked}`)}),X("content.tooltips")&&me(P("abbr[title]",e)).pipe(oe(u=>Ge(u,{viewport$})),Q(c)).subscribe(),yp(e,r).pipe($(u=>s.next(u)),V(()=>s.complete()),f(u=>H({ref:e},u)))})}function Gs(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return $t(et(`${r}/releases/latest`).pipe(_e(()=>y),f(n=>({version:n.tag_name})),ot({})),et(r).pipe(_e(()=>y),f(n=>({stars:n.stargazers_count,forks:n.forks_count})),ot({}))).pipe(f(([n,o])=>H(H({},n),o)))}else{let r=`https://api.github.com/users/${e}`;return et(r).pipe(f(n=>({repositories:n.public_repos})),ot({}))}}function Js(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return $t(et(`${r}/releases/permalink/latest`).pipe(_e(()=>y),f(({tag_name:n})=>({version:n})),ot({})),et(r).pipe(_e(()=>y),f(({star_count:n,forks_count:o})=>({stars:n,forks:o})),ot({}))).pipe(f(([n,o])=>H(H({},n),o)))}function Xs(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,n]=t;return Gs(r,n)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,n]=t;return Js(r,n)}return y}var xp;function wp(e){return xp||(xp=j(()=>{let t=__md_get("__source",sessionStorage);if(t)return Y(t);if(Ee("consent").length){let n=__md_get("__consent");if(!(n&&n.github))return y}return Xs(e.href).pipe($(n=>__md_set("__source",n,sessionStorage)))}).pipe(_e(()=>y),L(t=>Object.keys(t).length>0),f(t=>({facts:t})),se(1)))}function Zs(e){let t=G(":scope > :last-child",e);return j(()=>{let r=new I;return r.subscribe(({facts:n})=>{t.appendChild(bs(n)),t.classList.add("md-source__repository--active")}),wp(e).pipe($(n=>r.next(n)),V(()=>r.complete()),f(n=>H({ref:e},n)))})}function Ep(e,{viewport$:t,header$:r}){return Re(document.body).pipe(g(()=>Sn(e,{header$:r,viewport$:t})),f(({offset:{y:n}})=>({hidden:n>=10})),fe("hidden"))}function Qs(e,t){return j(()=>{let r=new I;return r.subscribe({next({hidden:n}){e.hidden=n},complete(){e.hidden=!1}}),(X("navigation.tabs.sticky")?Y({hidden:!1}):Ep(e,t)).pipe($(n=>r.next(n)),V(()=>r.complete()),f(n=>H({ref:e},n)))})}function Tp(e,{viewport$:t,header$:r}){let n=new Map,o=P(".md-nav__link",e);for(let s of o){let c=decodeURIComponent(s.hash.substring(1)),l=Le(`[id="${c}"]`);typeof l!="undefined"&&n.set(s,l)}let i=r.pipe(fe("height"),f(({height:s})=>{let c=ht("main"),l=G(":scope > :first-child",c);return s+.9*(l.offsetTop-c.offsetTop)}),xe());return Re(document.body).pipe(fe("height"),g(s=>j(()=>{let c=[];return Y([...n].reduce((l,[u,p])=>{for(;c.length&&n.get(c[c.length-1]).tagName>=p.tagName;)c.pop();let d=p.offsetTop;for(;!d&&p.parentElement;)p=p.parentElement,d=p.offsetTop;let m=p.offsetParent;for(;m;m=m.offsetParent)d+=m.offsetTop;return l.set([...c=[...c,u]].reverse(),d)},new Map))}).pipe(f(c=>new Map([...c].sort(([,l],[,u])=>l-u))),Ze(i),g(([c,l])=>t.pipe(Or(([u,p],{offset:{y:d},size:m})=>{let h=d+m.height>=Math.floor(s.height);for(;p.length;){let[,v]=p[0];if(v-l=d&&!h)p=[u.pop(),...p];else break}return[u,p]},[[],[...c]]),ie((u,p)=>u[0]===p[0]&&u[1]===p[1])))))).pipe(f(([s,c])=>({prev:s.map(([l])=>l),next:c.map(([l])=>l)})),J({prev:[],next:[]}),Pt(2,1),f(([s,c])=>s.prev.length{let i=new I,a=i.pipe(he(),ye(!0));if(i.subscribe(({prev:s,next:c})=>{for(let[l]of c)l.classList.remove("md-nav__link--passed"),l.classList.remove("md-nav__link--active");for(let[l,[u]]of s.entries())u.classList.add("md-nav__link--passed"),u.classList.toggle("md-nav__link--active",l===s.length-1)}),X("toc.follow")){let s=R(t.pipe(Be(1),f(()=>{})),t.pipe(Be(250),f(()=>"smooth")));i.pipe(L(({prev:c})=>c.length>0),Ze(n.pipe(Ie(ge))),pe(s)).subscribe(([[{prev:c}],l])=>{let[u]=c[c.length-1];if(u.offsetHeight){let p=ki(u);if(typeof p!="undefined"){let d=u.offsetTop-p.offsetTop,{height:m}=Ae(p);p.scrollTo({top:d-m/2,behavior:l})}}})}return X("navigation.tracking")&&t.pipe(Q(a),fe("offset"),Be(250),ke(1),Q(o.pipe(ke(1))),jt({delay:250}),pe(i)).subscribe(([,{prev:s}])=>{let c=Ye(),l=s[s.length-1];if(l&&l.length){let[u]=l,{hash:p}=new URL(u.href);c.hash!==p&&(c.hash=p,history.replaceState({},"",`${c}`))}else c.hash="",history.replaceState({},"",`${c}`)}),Tp(e,{viewport$:t,header$:r}).pipe($(s=>i.next(s)),V(()=>i.complete()),f(s=>H({ref:e},s)))})}function Sp(e,{viewport$:t,main$:r,target$:n}){let o=t.pipe(f(({offset:{y:a}})=>a),Pt(2,1),f(([a,s])=>a>s&&s>0),ie()),i=r.pipe(f(({active:a})=>a));return re([i,o]).pipe(f(([a,s])=>!(a&&s)),ie(),Q(n.pipe(ke(1))),ye(!0),jt({delay:250}),f(a=>({hidden:a})))}function tc(e,{viewport$:t,header$:r,main$:n,target$:o}){let i=new I,a=i.pipe(he(),ye(!0));return i.subscribe({next({hidden:s}){e.hidden=s,s?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(Q(a),fe("height")).subscribe(({height:s})=>{e.style.top=`${s+16}px`}),b(e,"click").subscribe(s=>{s.preventDefault(),window.scrollTo({top:0})}),Sp(e,{viewport$:t,main$:n,target$:o}).pipe($(s=>i.next(s)),V(()=>i.complete()),f(s=>H({ref:e},s)))}function rc(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,t.port&&(e.port=t.port),e}function Op(e,t){let r=new Map;for(let n of P("url",e)){let o=G("loc",n),i=[rc(new URL(o.textContent),t)];r.set(`${i[0]}`,i);for(let a of P("[rel=alternate]",n)){let s=a.getAttribute("href");s!=null&&i.push(rc(new URL(s),t))}}return r}function dr(e){return ns(new URL("sitemap.xml",e)).pipe(f(t=>Op(t,new URL(e))),_e(()=>Y(new Map)),xe())}function nc({document$:e}){let t=new Map;e.pipe(g(()=>P("link[rel=alternate]")),f(r=>new URL(r.href)),L(r=>!t.has(r.toString())),oe(r=>dr(r).pipe(f(n=>[r,n]),_e(()=>y)))).subscribe(([r,n])=>{t.set(r.toString().replace(/\/$/,""),n)}),b(document.body,"click").pipe(L(r=>!r.metaKey&&!r.ctrlKey),g(r=>{if(r.target instanceof Element){let n=r.target.closest("a");if(n&&!n.target){let o=[...t].find(([p])=>n.href.startsWith(`${p}/`));if(typeof o=="undefined")return y;let[i,a]=o,s=Ye();if(s.href.startsWith(i))return y;let c=Ue(),l=s.href.replace(c.base,"");l=`${i}/${l}`;let u=a.has(l.split("#")[0])?new URL(l,c.base):new URL(i);return r.preventDefault(),Y(u)}}return y})).subscribe(r=>dt(r,!0))}var Co=_r(Mo());function Lp(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function oc({alert$:e}){Co.default.isSupported()&&new U(t=>{new Co.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||Lp(G(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe($(t=>{t.trigger.focus()}),f(()=>Bt("clipboard.copied"))).subscribe(e)}function ic(e,t){if(!(e.target instanceof Element))return y;let r=e.target.closest("a");if(r===null)return y;if(r.target||e.metaKey||e.ctrlKey)return y;let n=new URL(r.href);return n.search=n.hash="",t.has(`${n}`)?(e.preventDefault(),Y(r)):y}function ac(e){let t=new Map;for(let r of P(":scope > *",e.head))t.set(r.outerHTML,r);return t}function sc(e){for(let t of P("[href], [src]",e))for(let r of["href","src"]){let n=t.getAttribute(r);if(n&&!/^(?:[a-z]+:)?\/\//i.test(n)){t[r]=t[r];break}}return Y(e)}function Mp(e){for(let n of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...X("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let o=Le(n),i=Le(n,e);typeof o!="undefined"&&typeof i!="undefined"&&o.replaceWith(i)}let t=ac(document);for(let[n,o]of ac(e))t.has(n)?t.delete(n):document.head.appendChild(o);for(let n of t.values()){let o=n.getAttribute("name");o!=="theme-color"&&o!=="color-scheme"&&n.remove()}let r=ht("container");return nt(P("script",r)).pipe(g(n=>{let o=e.createElement("script");if(n.src){for(let i of n.getAttributeNames())o.setAttribute(i,n.getAttribute(i));return n.replaceWith(o),new U(i=>{o.onload=()=>i.complete()})}else return o.textContent=n.textContent,n.replaceWith(o),y}),he(),ye(document))}function cc({sitemap$:e,location$:t,viewport$:r,progress$:n}){if(location.protocol==="file:")return Ke;Y(document).subscribe(sc);let o=b(document.body,"click").pipe(Ze(e),g(([s,c])=>ic(s,c)),f(({href:s})=>new URL(s)),xe()),i=b(window,"popstate").pipe(f(Ye),xe());o.pipe(pe(r)).subscribe(([s,{offset:c}])=>{history.replaceState(c,""),history.pushState(null,"",s)}),R(o,i).subscribe(t);let a=t.pipe(fe("pathname"),g(s=>En(s,{progress$:n}).pipe(_e(()=>(dt(s,!0),y)))),g(sc),g(Mp),xe());return R(a.pipe(pe(t,(s,c)=>c)),a.pipe(g(()=>t),fe("hash")),t.pipe(ie((s,c)=>s.pathname===c.pathname&&s.hash===c.hash),g(()=>o),$(()=>history.back()))).subscribe(s=>{var c,l;history.state!==null||!s.hash?window.scrollTo(0,(l=(c=history.state)==null?void 0:c.y)!=null?l:0):(history.scrollRestoration="auto",es(s.hash),history.scrollRestoration="manual")}),t.subscribe(()=>{history.scrollRestoration="manual"}),b(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),r.pipe(fe("offset"),Be(100)).subscribe(({offset:s})=>{history.replaceState(s,"")}),X("navigation.instant.prefetch")&&R(b(document.body,"mousemove"),b(document.body,"focusin")).pipe(Ze(e),g(([s,c])=>ic(s,c)),Be(25),Yn(({href:s})=>s),cn(s=>{let c=document.createElement("link");return c.rel="prefetch",c.href=s.toString(),document.head.appendChild(c),b(c,"load").pipe(f(()=>c),Me(1))})).subscribe(s=>s.remove()),a}function lc(e){var u;let{selectedVersionSitemap:t,selectedVersionBaseURL:r,currentLocation:n,currentBaseURL:o}=e,i=(u=Ho(o))==null?void 0:u.pathname;if(i===void 0)return;let a=kp(n.pathname,i);if(a===void 0)return;let s=Cp(t.keys());if(!t.has(s))return;let c=Ho(a,s);if(!c||!t.has(c.href))return;let l=Ho(a,r);if(l)return l.hash=n.hash,l.search=n.search,l}function Ho(e,t){try{return new URL(e,t)}catch(r){return}}function kp(e,t){if(e.startsWith(t))return e.slice(t.length)}function Ap(e,t){let r=Math.min(e.length,t.length),n;for(n=0;ny)),n=r.pipe(f(o=>{let[,i]=t.base.match(/([^/]+)\/?$/);return o.find(({version:a,aliases:s})=>a===i||s.includes(i))||o[0]}));r.pipe(f(o=>new Map(o.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),g(o=>b(document.body,"click").pipe(L(i=>!i.metaKey&&!i.ctrlKey),pe(n),g(([i,a])=>{if(i.target instanceof Element){let s=i.target.closest("a");if(s&&!s.target&&o.has(s.href)){let c=s.href;return!i.target.closest(".md-version")&&o.get(c)===a?y:(i.preventDefault(),Y(new URL(c)))}}return y}),g(i=>dr(i).pipe(f(a=>{var s;return(s=lc({selectedVersionSitemap:a,selectedVersionBaseURL:i,currentLocation:Ye(),currentBaseURL:t.base}))!=null?s:i})))))).subscribe(o=>dt(o,!0)),re([r,n]).subscribe(([o,i])=>{G(".md-header__topic").appendChild(_s(o,i))}),e.pipe(g(()=>n)).subscribe(o=>{var s;let i=new URL(t.base),a=__md_get("__outdated",sessionStorage,i);if(a===null){a=!0;let c=((s=t.version)==null?void 0:s.default)||"latest";Array.isArray(c)||(c=[c]);e:for(let l of c)for(let u of o.aliases.concat(o.version))if(new RegExp(l,"i").test(u)){a=!1;break e}__md_set("__outdated",a,sessionStorage,i)}if(a)for(let c of Ee("outdated"))c.hidden=!1})}function pc({document$:e,viewport$:t}){e.pipe(g(()=>P(".md-ellipsis")),oe(r=>Et(r).pipe(Q(e.pipe(ke(1))),L(n=>n),f(()=>r),Me(1))),L(r=>r.offsetWidth{let n=r.innerText,o=r.closest("a")||r;return o.title=n,X("content.tooltips")?Ge(o,{viewport$:t}).pipe(Q(e.pipe(ke(1))),V(()=>o.removeAttribute("title"))):y})).subscribe(),X("content.tooltips")&&e.pipe(g(()=>P(".md-status")),oe(r=>Ge(r,{viewport$:t}))).subscribe()}function fc({document$:e,tablet$:t}){e.pipe(g(()=>P(".md-toggle--indeterminate")),$(r=>{r.indeterminate=!0,r.checked=!1}),oe(r=>b(r,"change").pipe(Xn(()=>r.classList.contains("md-toggle--indeterminate")),f(()=>r))),pe(t)).subscribe(([r,n])=>{r.classList.remove("md-toggle--indeterminate"),n&&(r.checked=!1)})}function Hp(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function mc({document$:e}){e.pipe(g(()=>P("[data-md-scrollfix]")),$(t=>t.removeAttribute("data-md-scrollfix")),L(Hp),oe(t=>b(t,"touchstart").pipe(f(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let n=e[r];typeof n=="string"?n=document.createTextNode(n):n.parentNode&&n.parentNode.removeChild(n),r?t.insertBefore(this.previousSibling,n):t.replaceChild(n,this)}}}));function $p(){return location.protocol==="file:"?ar(`${new URL("search.js",Mn.base)}`).pipe(f(()=>__index),_e(()=>Ke),se(1)):et(new URL("search.json",Mn.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var vt=Si(),Ur=Za(),hr=ts(Ur),hc=Xa(),ze=cs(),$o=Ir("(min-width: 60em)"),vc=Ir("(min-width: 76.25em)"),bc=rs(),Mn=Ue(),gc=Le(".md-search")?$p():Ke,Po=new I;oc({alert$:Po});nc({document$:vt});var Io=new I,_c=dr(Mn.base);X("navigation.instant")&&cc({sitemap$:_c,location$:Ur,viewport$:ze,progress$:Io}).subscribe(vt);var dc;((dc=Mn.version)==null?void 0:dc.provider)==="mike"&&uc({document$:vt});R(Ur,hr).pipe(It(125)).subscribe(()=>{Eo("drawer",!1),Eo("search",!1)});hc.pipe(L(({mode:e,meta:t})=>e==="global"&&!t)).subscribe(e=>{switch(e.type){case",":case"p":let t=document.querySelector("link[rel=prev]");t instanceof HTMLLinkElement&&dt(t);break;case".":case"n":let r=document.querySelector("link[rel=next]");r instanceof HTMLLinkElement&&dt(r);break;case"/":let n=document.querySelector("[data-md-component=search] button");n instanceof HTMLButtonElement&&n.click();break;case"Enter":let o=xt();o instanceof HTMLLabelElement&&o.click()}});pc({viewport$:ze,document$:vt});fc({document$:vt,tablet$:$o});mc({document$:vt});var Lt=Us(ht("header"),{viewport$:ze}),Fr=vt.pipe(f(()=>ht("main")),g(e=>Ws(e,{viewport$:ze,header$:Lt})),se(1)),Pp=R(...Ee("consent").map(e=>us(e,{target$:hr})),...Ee("dialog").map(e=>Fs(e,{alert$:Po})),...Ee("palette").map(e=>Vs(e)),...Ee("progress").map(e=>zs(e,{progress$:Io})),...Ee("search").map(e=>Ks(e,{index$:gc})),...Ee("source").map(e=>Zs(e))),Ip=j(()=>R(...Ee("announce").map(e=>ls(e)),...Ee("content").map(e=>js(e,{sitemap$:_c,viewport$:ze,target$:hr,print$:bc})),...Ee("content").map(e=>X("search.highlight")?Ys(e,{index$:gc,location$:Ur}):y),...Ee("header").map(e=>Ns(e,{viewport$:ze,header$:Lt,main$:Fr})),...Ee("header-title").map(e=>Ds(e,{viewport$:ze,header$:Lt})),...Ee("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?yo(vc,()=>Ao(e,{viewport$:ze,header$:Lt,main$:Fr})):yo($o,()=>Ao(e,{viewport$:ze,header$:Lt,main$:Fr}))),...Ee("tabs").map(e=>Qs(e,{viewport$:ze,header$:Lt})),...Ee("toc").map(e=>ec(e,{viewport$:ze,header$:Lt,main$:Fr,target$:hr})),...Ee("top").map(e=>tc(e,{viewport$:ze,header$:Lt,main$:Fr,target$:hr})))),yc=vt.pipe(g(()=>Ip),Rt(Pp),se(1));yc.subscribe();window.document$=vt;window.location$=Ur;window.target$=hr;window.keyboard$=hc;window.viewport$=ze;window.tablet$=$o;window.screen$=vc;window.print$=bc;window.alert$=Po;window.progress$=Io;window.component$=yc;})(); -/*! update cache: 20260329054721 */ +/*! update cache: 20260330081538 */ diff --git a/en/404.html b/en/404.html index dffc401e1..26452ec4b 100644 --- a/en/404.html +++ b/en/404.html @@ -59,8 +59,8 @@ - - + + diff --git a/en/assets/javascripts/bundle.c2b142ea.min.js b/en/assets/javascripts/bundle.c2b142ea.min.js index 8c73d00f8..2404a9147 100644 --- a/en/assets/javascripts/bundle.c2b142ea.min.js +++ b/en/assets/javascripts/bundle.c2b142ea.min.js @@ -1,4 +1,4 @@ "use strict";(()=>{var xc=Object.create;var kn=Object.defineProperty,wc=Object.defineProperties,Ec=Object.getOwnPropertyDescriptor,Tc=Object.getOwnPropertyDescriptors,Sc=Object.getOwnPropertyNames,Dr=Object.getOwnPropertySymbols,Oc=Object.getPrototypeOf,An=Object.prototype.hasOwnProperty,Fo=Object.prototype.propertyIsEnumerable;var jo=(e,t,r)=>t in e?kn(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,H=(e,t)=>{for(var r in t||(t={}))An.call(t,r)&&jo(e,r,t[r]);if(Dr)for(var r of Dr(t))Fo.call(t,r)&&jo(e,r,t[r]);return e},He=(e,t)=>wc(e,Tc(t));var gr=(e,t)=>{var r={};for(var n in e)An.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&Dr)for(var n of Dr(e))t.indexOf(n)<0&&Fo.call(e,n)&&(r[n]=e[n]);return r};var Cn=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Lc=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Sc(t))!An.call(e,o)&&o!==r&&kn(e,o,{get:()=>t[o],enumerable:!(n=Ec(t,o))||n.enumerable});return e};var _r=(e,t,r)=>(r=e!=null?xc(Oc(e)):{},Lc(t||!e||!e.__esModule?kn(r,"default",{value:e,enumerable:!0}):r,e));var Uo=(e,t,r)=>new Promise((n,o)=>{var i=c=>{try{s(r.next(c))}catch(l){o(l)}},a=c=>{try{s(r.throw(c))}catch(l){o(l)}},s=c=>c.done?n(c.value):Promise.resolve(c.value).then(i,a);s((r=r.apply(e,t)).next())});var Do=Cn((Hn,No)=>{(function(e,t){typeof Hn=="object"&&typeof No!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(Hn,(function(){"use strict";function e(r){var n=!0,o=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(_){return!!(_&&_!==document&&_.nodeName!=="HTML"&&_.nodeName!=="BODY"&&"classList"in _&&"contains"in _.classList)}function c(_){var de=_.type,be=_.tagName;return!!(be==="INPUT"&&a[de]&&!_.readOnly||be==="TEXTAREA"&&!_.readOnly||_.isContentEditable)}function l(_){_.classList.contains("focus-visible")||(_.classList.add("focus-visible"),_.setAttribute("data-focus-visible-added",""))}function u(_){_.hasAttribute("data-focus-visible-added")&&(_.classList.remove("focus-visible"),_.removeAttribute("data-focus-visible-added"))}function p(_){_.metaKey||_.altKey||_.ctrlKey||(s(r.activeElement)&&l(r.activeElement),n=!0)}function d(_){n=!1}function m(_){s(_.target)&&(n||c(_.target))&&l(_.target)}function h(_){s(_.target)&&(_.target.classList.contains("focus-visible")||_.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),u(_.target))}function v(_){document.visibilityState==="hidden"&&(o&&(n=!0),x())}function x(){document.addEventListener("mousemove",E),document.addEventListener("mousedown",E),document.addEventListener("mouseup",E),document.addEventListener("pointermove",E),document.addEventListener("pointerdown",E),document.addEventListener("pointerup",E),document.addEventListener("touchmove",E),document.addEventListener("touchstart",E),document.addEventListener("touchend",E)}function w(){document.removeEventListener("mousemove",E),document.removeEventListener("mousedown",E),document.removeEventListener("mouseup",E),document.removeEventListener("pointermove",E),document.removeEventListener("pointerdown",E),document.removeEventListener("pointerup",E),document.removeEventListener("touchmove",E),document.removeEventListener("touchstart",E),document.removeEventListener("touchend",E)}function E(_){_.target.nodeName&&_.target.nodeName.toLowerCase()==="html"||(n=!1,w())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",d,!0),document.addEventListener("pointerdown",d,!0),document.addEventListener("touchstart",d,!0),document.addEventListener("visibilitychange",v,!0),x(),r.addEventListener("focus",m,!0),r.addEventListener("blur",h,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)}))});var So=Cn((M0,vs)=>{"use strict";var Gu=/["'&<>]/;vs.exports=Ju;function Ju(e){var t=""+e,r=Gu.exec(t);if(!r)return t;var n,o="",i=0,a=0;for(i=r.index;i{(function(t,r){typeof jr=="object"&&typeof Lo=="object"?Lo.exports=r():typeof define=="function"&&define.amd?define([],r):typeof jr=="object"?jr.ClipboardJS=r():t.ClipboardJS=r()})(jr,function(){return(function(){var e={686:(function(n,o,i){"use strict";i.d(o,{default:function(){return vr}});var a=i(279),s=i.n(a),c=i(370),l=i.n(c),u=i(817),p=i.n(u);function d(B){try{return document.execCommand(B)}catch(C){return!1}}var m=function(C){var k=p()(C);return d("cut"),k},h=m;function v(B){var C=document.documentElement.getAttribute("dir")==="rtl",k=document.createElement("textarea");k.style.fontSize="12pt",k.style.border="0",k.style.padding="0",k.style.margin="0",k.style.position="absolute",k.style[C?"right":"left"]="-9999px";var D=window.pageYOffset||document.documentElement.scrollTop;return k.style.top="".concat(D,"px"),k.setAttribute("readonly",""),k.value=B,k}var x=function(C,k){var D=v(C);k.container.appendChild(D);var W=p()(D);return d("copy"),D.remove(),W},w=function(C){var k=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},D="";return typeof C=="string"?D=x(C,k):C instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(C==null?void 0:C.type)?D=x(C.value,k):(D=p()(C),d("copy")),D},E=w;function _(B){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?_=function(k){return typeof k}:_=function(k){return k&&typeof Symbol=="function"&&k.constructor===Symbol&&k!==Symbol.prototype?"symbol":typeof k},_(B)}var de=function(){var C=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},k=C.action,D=k===void 0?"copy":k,W=C.container,Z=C.target,We=C.text;if(D!=="copy"&&D!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(Z!==void 0)if(Z&&_(Z)==="object"&&Z.nodeType===1){if(D==="copy"&&Z.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(D==="cut"&&(Z.hasAttribute("readonly")||Z.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(We)return E(We,{container:W});if(Z)return D==="cut"?h(Z):E(Z,{container:W})},be=de;function M(B){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?M=function(k){return typeof k}:M=function(k){return k&&typeof Symbol=="function"&&k.constructor===Symbol&&k!==Symbol.prototype?"symbol":typeof k},M(B)}function O(B,C){if(!(B instanceof C))throw new TypeError("Cannot call a class as a function")}function N(B,C){for(var k=0;k0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof W.action=="function"?W.action:this.defaultAction,this.target=typeof W.target=="function"?W.target:this.defaultTarget,this.text=typeof W.text=="function"?W.text:this.defaultText,this.container=M(W.container)==="object"?W.container:document.body}},{key:"listenClick",value:function(W){var Z=this;this.listener=l()(W,"click",function(We){return Z.onClick(We)})}},{key:"onClick",value:function(W){var Z=W.delegateTarget||W.currentTarget,We=this.action(Z)||"copy",Gt=be({action:We,container:this.container,target:this.target(Z),text:this.text(Z)});this.emit(Gt?"success":"error",{action:We,text:Gt,trigger:Z,clearSelection:function(){Z&&Z.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(W){return Yt("action",W)}},{key:"defaultTarget",value:function(W){var Z=Yt("target",W);if(Z)return document.querySelector(Z)}},{key:"defaultText",value:function(W){return Yt("text",W)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(W){var Z=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return E(W,Z)}},{key:"cut",value:function(W){return h(W)}},{key:"isSupported",value:function(){var W=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],Z=typeof W=="string"?[W]:W,We=!!document.queryCommandSupported;return Z.forEach(function(Gt){We=We&&!!document.queryCommandSupported(Gt)}),We}}]),k})(s()),vr=Mt}),828:(function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,c){for(;s&&s.nodeType!==o;){if(typeof s.matches=="function"&&s.matches(c))return s;s=s.parentNode}}n.exports=a}),438:(function(n,o,i){var a=i(828);function s(u,p,d,m,h){var v=l.apply(this,arguments);return u.addEventListener(d,v,h),{destroy:function(){u.removeEventListener(d,v,h)}}}function c(u,p,d,m,h){return typeof u.addEventListener=="function"?s.apply(null,arguments):typeof d=="function"?s.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(v){return s(v,p,d,m,h)}))}function l(u,p,d,m){return function(h){h.delegateTarget=a(h.target,p),h.delegateTarget&&m.call(u,h)}}n.exports=c}),879:(function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}}),370:(function(n,o,i){var a=i(879),s=i(438);function c(d,m,h){if(!d&&!m&&!h)throw new Error("Missing required arguments");if(!a.string(m))throw new TypeError("Second argument must be a String");if(!a.fn(h))throw new TypeError("Third argument must be a Function");if(a.node(d))return l(d,m,h);if(a.nodeList(d))return u(d,m,h);if(a.string(d))return p(d,m,h);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function l(d,m,h){return d.addEventListener(m,h),{destroy:function(){d.removeEventListener(m,h)}}}function u(d,m,h){return Array.prototype.forEach.call(d,function(v){v.addEventListener(m,h)}),{destroy:function(){Array.prototype.forEach.call(d,function(v){v.removeEventListener(m,h)})}}}function p(d,m,h){return s(document.body,d,m,h)}n.exports=c}),817:(function(n){function o(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var c=window.getSelection(),l=document.createRange();l.selectNodeContents(i),c.removeAllRanges(),c.addRange(l),a=c.toString()}return a}n.exports=o}),279:(function(n){function o(){}o.prototype={on:function(i,a,s){var c=this.e||(this.e={});return(c[i]||(c[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var c=this;function l(){c.off(i,l),a.apply(s,arguments)}return l._=a,this.on(i,l,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),c=0,l=s.length;for(c;c0&&i[i.length-1])&&(l[0]===6||l[0]===2)){r=0;continue}if(l[0]===3&&(!i||l[1]>i[0]&&l[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function te(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var n=r.call(e),o,i=[],a;try{for(;(t===void 0||t-- >0)&&!(o=n.next()).done;)i.push(o.value)}catch(s){a={error:s}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(a)throw a.error}}return i}function ne(e,t,r){if(r||arguments.length===2)for(var n=0,o=t.length,i;n1||c(m,v)})},h&&(o[m]=h(o[m])))}function c(m,h){try{l(n[m](h))}catch(v){d(i[0][3],v)}}function l(m){m.value instanceof kt?Promise.resolve(m.value.v).then(u,p):d(i[0][2],m)}function u(m){c("next",m)}function p(m){c("throw",m)}function d(m,h){m(h),i.shift(),i.length&&c(i[0][0],i[0][1])}}function zo(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof $e=="function"?$e(e):e[Symbol.iterator](),r={},n("next"),n("throw"),n("return"),r[Symbol.asyncIterator]=function(){return this},r);function n(i){r[i]=e[i]&&function(a){return new Promise(function(s,c){a=e[i](a),o(s,c,a.done,a.value)})}}function o(i,a,s,c){Promise.resolve(c).then(function(l){i({value:l,done:s})},a)}}function F(e){return typeof e=="function"}function Jt(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var Vr=Jt(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: `+r.map(function(n,o){return o+1+") "+n.toString()}).join(` `):"",this.name="UnsubscriptionError",this.errors=r}});function ct(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var rt=(function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=$e(a),c=s.next();!c.done;c=s.next()){var l=c.value;l.remove(this)}}catch(v){t={error:v}}finally{try{c&&!c.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var u=this.initialTeardown;if(F(u))try{u()}catch(v){i=v instanceof Vr?v.errors:[v]}var p=this._finalizers;if(p){this._finalizers=null;try{for(var d=$e(p),m=d.next();!m.done;m=d.next()){var h=m.value;try{qo(h)}catch(v){i=i!=null?i:[],v instanceof Vr?i=ne(ne([],te(i)),te(v.errors)):i.push(v)}}}catch(v){n={error:v}}finally{try{m&&!m.done&&(o=d.return)&&o.call(d)}finally{if(n)throw n.error}}}if(i)throw new Vr(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)qo(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&ct(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&ct(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=(function(){var t=new e;return t.closed=!0,t})(),e})();var Pn=rt.EMPTY;function zr(e){return e instanceof rt||e&&"closed"in e&&F(e.remove)&&F(e.add)&&F(e.unsubscribe)}function qo(e){F(e)?e():e.unsubscribe()}var Je={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var Xt={setTimeout:function(e,t){for(var r=[],n=2;n0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,a=o.isStopped,s=o.observers;return i||a?Pn:(this.currentObservers=null,s.push(r),new rt(function(){n.currentObservers=null,ct(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,a=n.isStopped;o?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new U;return r.source=this,r},t.create=function(r,n){return new Qo(r,n)},t})(U);var Qo=(function(e){ue(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:Pn},t})(I);var Un=(function(e){ue(t,e);function t(r){var n=e.call(this)||this;return n._value=r,n}return Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!1,configurable:!0}),t.prototype._subscribe=function(r){var n=e.prototype._subscribe.call(this,r);return!n.closed&&r.next(this._value),n},t.prototype.getValue=function(){var r=this,n=r.hasError,o=r.thrownError,i=r._value;if(n)throw o;return this._throwIfClosed(),i},t.prototype.next=function(r){e.prototype.next.call(this,this._value=r)},t})(I);var xr={now:function(){return(xr.delegate||Date).now()},delegate:void 0};var wr=(function(e){ue(t,e);function t(r,n,o){r===void 0&&(r=1/0),n===void 0&&(n=1/0),o===void 0&&(o=xr);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=n,i._timestampProvider=o,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=n===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n._buffer,a=n._infiniteTimeWindow,s=n._timestampProvider,c=n._windowTime;o||(i.push(r),!a&&i.push(s.now()+c)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o._infiniteTimeWindow,a=o._buffer,s=a.slice(),c=0;c0?e.prototype.schedule.call(this,r,n):(this.delay=n,this.state=r,this.scheduler.flush(this),this)},t.prototype.execute=function(r,n){return n>0||this.closed?e.prototype.execute.call(this,r,n):this._execute(r,n)},t.prototype.requestAsyncId=function(r,n,o){return o===void 0&&(o=0),o!=null&&o>0||o==null&&this.delay>0?e.prototype.requestAsyncId.call(this,r,n,o):(r.flush(this),0)},t})(tr);var ri=(function(e){ue(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t})(rr);var Wn=new ri(ti);var ni=(function(e){ue(t,e);function t(r,n){var o=e.call(this,r,n)||this;return o.scheduler=r,o.work=n,o}return t.prototype.requestAsyncId=function(r,n,o){return o===void 0&&(o=0),o!==null&&o>0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r._scheduled||(r._scheduled=er.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){var i;if(o===void 0&&(o=0),o!=null?o>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);var a=r.actions;n!=null&&n===r._scheduled&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==n&&(er.cancelAnimationFrame(n),r._scheduled=void 0)},t})(tr);var oi=(function(e){ue(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var n;r?n=r.id:(n=this._scheduled,this._scheduled=void 0);var o=this.actions,i;r=r||o.shift();do if(i=r.execute(r.state,r.delay))break;while((r=o[0])&&r.id===n&&o.shift());if(this._active=!1,i){for(;(r=o[0])&&r.id===n&&o.shift();)r.unsubscribe();throw i}},t})(rr);var je=new oi(ni);var y=new U(function(e){return e.complete()});function Br(e){return e&&F(e.schedule)}function Vn(e){return e[e.length-1]}function _t(e){return F(Vn(e))?e.pop():void 0}function qe(e){return Br(Vn(e))?e.pop():void 0}function Yr(e,t){return typeof Vn(e)=="number"?e.pop():t}var nr=(function(e){return e&&typeof e.length=="number"&&typeof e!="function"});function Gr(e){return F(e==null?void 0:e.then)}function Jr(e){return F(e[Qt])}function Xr(e){return Symbol.asyncIterator&&F(e==null?void 0:e[Symbol.asyncIterator])}function Zr(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Rc(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Qr=Rc();function en(e){return F(e==null?void 0:e[Qr])}function tn(e){return Vo(this,arguments,function(){var r,n,o,i;return Wr(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,kt(r.read())];case 3:return n=a.sent(),o=n.value,i=n.done,i?[4,kt(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,kt(o)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function rn(e){return F(e==null?void 0:e.getReader)}function q(e){if(e instanceof U)return e;if(e!=null){if(Jr(e))return jc(e);if(nr(e))return Fc(e);if(Gr(e))return Uc(e);if(Xr(e))return ii(e);if(en(e))return Nc(e);if(rn(e))return Dc(e)}throw Zr(e)}function jc(e){return new U(function(t){var r=e[Qt]();if(F(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function Fc(e){return new U(function(t){for(var r=0;r=2;return function(n){return n.pipe(e?L(function(o,i){return e(o,i,n)}):Oe,Me(1),r?ot(t):wi(function(){return new on}))}}function Gn(e){return e<=0?function(){return y}:S(function(t,r){var n=[];t.subscribe(T(r,function(o){n.push(o),e=2,!0))}function xe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new I}:t,n=e.resetOnError,o=n===void 0?!0:n,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,c=s===void 0?!0:s;return function(l){var u,p,d,m=0,h=!1,v=!1,x=function(){p==null||p.unsubscribe(),p=void 0},w=function(){x(),u=d=void 0,h=v=!1},E=function(){var _=u;w(),_==null||_.unsubscribe()};return S(function(_,de){m++,!v&&!h&&x();var be=d=d!=null?d:r();de.add(function(){m--,m===0&&!v&&!h&&(p=Jn(E,c))}),be.subscribe(de),!u&&m>0&&(u=new Ct({next:function(M){return be.next(M)},error:function(M){v=!0,x(),p=Jn(w,o,M),be.error(M)},complete:function(){h=!0,x(),p=Jn(w,a),be.complete()}}),q(_).subscribe(u))})(l)}}function Jn(e,t){for(var r=[],n=2;ne.next(document)),e}function P(e,t=document){return Array.from(t.querySelectorAll(e))}function G(e,t=document){let r=Le(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function Le(e,t=document){return t.querySelector(e)||void 0}function xt(){var e,t,r,n;return(n=(r=(t=(e=document.activeElement)==null?void 0:e.shadowRoot)==null?void 0:t.activeElement)!=null?r:document.activeElement)!=null?n:void 0}var il=R(b(document.body,"focusin"),b(document.body,"focusout")).pipe(Be(1),J(void 0),f(()=>xt()||document.body),se(1));function ir(e){return il.pipe(f(t=>e.contains(t)),ie())}function Ft(e,t){let{matches:r}=matchMedia("(hover)");return j(()=>(r?R(b(e,"mouseenter").pipe(f(()=>!0)),b(e,"mouseleave").pipe(f(()=>!1))):R(b(e,"touchstart").pipe(f(()=>!0)),b(e,"touchend").pipe(f(()=>!1)),b(e,"touchcancel").pipe(f(()=>!1)))).pipe(t?Tr(o=>Ve(+!o*t)):Oe,J(!0,e.matches(":hover"))))}function Oi(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Oi(e,r)}function A(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="undefined"&&(typeof t[o]!="boolean"?n.setAttribute(o,t[o]):n.setAttribute(o,""));for(let o of r)Oi(n,o);return n}function Li(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function ar(e){let t=A("script",{src:e});return j(()=>(document.head.appendChild(t),R(b(t,"load"),b(t,"error").pipe(g(()=>zn(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(f(()=>{}),V(()=>document.head.removeChild(t)),Me(1))))}var Mi=new I,al=j(()=>typeof ResizeObserver=="undefined"?ar("https://unpkg.com/resize-observer-polyfill"):Y(void 0)).pipe(f(()=>new ResizeObserver(e=>e.forEach(t=>Mi.next(t)))),g(e=>R(Ke,Y(e)).pipe(V(()=>e.disconnect()))),se(1));function Ae(e){return{width:e.offsetWidth,height:e.offsetHeight}}function Re(e){let t=e;for(;t.clientWidth===0&&t.parentElement;)t=t.parentElement;return al.pipe($(r=>r.observe(t)),g(r=>Mi.pipe(L(n=>n.target===t),V(()=>r.unobserve(t)))),f(()=>Ae(e)),J(Ae(e)))}function Mr(e){return{width:e.scrollWidth,height:e.scrollHeight}}function ki(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}function Ai(e){let t=[],r=e.parentElement;for(;r;)(e.clientWidth>r.clientWidth||e.clientHeight>r.clientHeight)&&t.push(r),r=(e=r).parentElement;return t.length===0&&t.push(document.documentElement),t}function wt(e){return{x:e.offsetLeft,y:e.offsetTop}}function Ci(e){let t=e.getBoundingClientRect();return{x:t.x+window.scrollX,y:t.y+window.scrollY}}function Hi(e){return R(b(window,"load"),b(window,"resize")).pipe(Xe(0,je),f(()=>wt(e)),J(wt(e)))}function ln(e){return{x:e.scrollLeft,y:e.scrollTop}}function Ut(e){return R(b(e,"scroll"),b(window,"scroll"),b(window,"resize")).pipe(Xe(0,je),f(()=>ln(e)),J(ln(e)))}var $i=new I,sl=j(()=>Y(new IntersectionObserver(e=>{for(let t of e)$i.next(t)},{threshold:0}))).pipe(g(e=>R(Ke,Y(e)).pipe(V(()=>e.disconnect()))),se(1));function Et(e){return sl.pipe($(t=>t.observe(e)),g(t=>$i.pipe(L(({target:r})=>r===e),V(()=>t.unobserve(e)),f(({isIntersecting:r})=>r))))}var cl=Object.create,la=Object.defineProperty,ll=Object.getOwnPropertyDescriptor,ul=Object.getOwnPropertyNames,pl=Object.getPrototypeOf,fl=Object.prototype.hasOwnProperty,ml=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),dl=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of ul(t))!fl.call(e,o)&&o!==r&&la(e,o,{get:()=>t[o],enumerable:!(n=ll(t,o))||n.enumerable});return e},hl=(e,t,r)=>(r=e!=null?cl(pl(e)):{},dl(t||!e||!e.__esModule?la(r,"default",{value:e,enumerable:!0}):r,e)),vl=ml((e,t)=>{var r="Expected a function",n=NaN,o="[object Symbol]",i=/^\s+|\s+$/g,a=/^[-+]0x[0-9a-f]+$/i,s=/^0b[01]+$/i,c=/^0o[0-7]+$/i,l=parseInt,u=typeof global=="object"&&global&&global.Object===Object&&global,p=typeof self=="object"&&self&&self.Object===Object&&self,d=u||p||Function("return this")(),m=Object.prototype,h=m.toString,v=Math.max,x=Math.min,w=function(){return d.Date.now()};function E(O,N,ee){var le,ce,Ne,bt,De,st,tt=0,Yt=!1,Mt=!1,vr=!0;if(typeof O!="function")throw new TypeError(r);N=M(N)||0,_(ee)&&(Yt=!!ee.leading,Mt="maxWait"in ee,Ne=Mt?v(M(ee.maxWait)||0,N):Ne,vr="trailing"in ee?!!ee.trailing:vr);function B(Te){var gt=le,br=ce;return le=ce=void 0,tt=Te,bt=O.apply(br,gt),bt}function C(Te){return tt=Te,De=setTimeout(W,N),Yt?B(Te):bt}function k(Te){var gt=Te-st,br=Te-tt,Ro=N-gt;return Mt?x(Ro,Ne-br):Ro}function D(Te){var gt=Te-st,br=Te-tt;return st===void 0||gt>=N||gt<0||Mt&&br>=Ne}function W(){var Te=w();if(D(Te))return Z(Te);De=setTimeout(W,k(Te))}function Z(Te){return De=void 0,vr&&le?B(Te):(le=ce=void 0,bt)}function We(){De!==void 0&&clearTimeout(De),tt=0,le=st=ce=De=void 0}function Gt(){return De===void 0?bt:Z(w())}function Nr(){var Te=w(),gt=D(Te);if(le=arguments,ce=this,st=Te,gt){if(De===void 0)return C(st);if(Mt)return De=setTimeout(W,N),B(st)}return De===void 0&&(De=setTimeout(W,N)),bt}return Nr.cancel=We,Nr.flush=Gt,Nr}function _(O){var N=typeof O;return!!O&&(N=="object"||N=="function")}function de(O){return!!O&&typeof O=="object"}function be(O){return typeof O=="symbol"||de(O)&&h.call(O)==o}function M(O){if(typeof O=="number")return O;if(be(O))return n;if(_(O)){var N=typeof O.valueOf=="function"?O.valueOf():O;O=_(N)?N+"":N}if(typeof O!="string")return O===0?O:+O;O=O.replace(i,"");var ee=s.test(O);return ee||c.test(O)?l(O.slice(2),ee?2:8):a.test(O)?n:+O}t.exports=E}),yn,K,ua,pa,Nt,Pi,fa,ma,da,lo,to,ro,bl,Ar={},ha=[],gl=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,Pr=Array.isArray;function pt(e,t){for(var r in t)e[r]=t[r];return e}function uo(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function Wt(e,t,r){var n,o,i,a={};for(i in t)i=="key"?n=t[i]:i=="ref"?o=t[i]:a[i]=t[i];if(arguments.length>2&&(a.children=arguments.length>3?yn.call(arguments,2):r),typeof e=="function"&&e.defaultProps!=null)for(i in e.defaultProps)a[i]===void 0&&(a[i]=e.defaultProps[i]);return fn(e,a,n,o,null)}function fn(e,t,r,n,o){var i={type:e,props:t,key:r,ref:n,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:o!=null?o:++ua,__i:-1,__u:0};return o==null&&K.vnode!=null&&K.vnode(i),i}function ft(e){return e.children}function at(e,t){this.props=e,this.context=t}function cr(e,t){if(t==null)return e.__?cr(e.__,e.__i+1):null;for(var r;ts&&Nt.sort(ma),e=Nt.shift(),s=Nt.length,e.__d&&(r=void 0,n=void 0,o=(n=(t=e).__v).__e,i=[],a=[],t.__P&&((r=pt({},n)).__v=n.__v+1,K.vnode&&K.vnode(r),po(t.__P,r,n,t.__n,t.__P.namespaceURI,32&n.__u?[o]:null,i,o!=null?o:cr(n),!!(32&n.__u),a),r.__v=n.__v,r.__.__k[r.__i]=r,_a(i,r,a),n.__e=n.__=null,r.__e!=o&&va(r)));vn.__r=0}function ba(e,t,r,n,o,i,a,s,c,l,u){var p,d,m,h,v,x,w,E=n&&n.__k||ha,_=t.length;for(c=_l(r,t,E,c,_),p=0;p<_;p++)(m=r.__k[p])!=null&&(d=m.__i==-1?Ar:E[m.__i]||Ar,m.__i=p,x=po(e,m,d,o,i,a,s,c,l,u),h=m.__e,m.ref&&d.ref!=m.ref&&(d.ref&&fo(d.ref,null,m),u.push(m.ref,m.__c||h,m)),v==null&&h!=null&&(v=h),(w=!!(4&m.__u))||d.__k===m.__k?c=ga(m,c,e,w):typeof m.type=="function"&&x!==void 0?c=x:h&&(c=h.nextSibling),m.__u&=-7);return r.__e=v,c}function _l(e,t,r,n,o){var i,a,s,c,l,u=r.length,p=u,d=0;for(e.__k=new Array(o),i=0;i0?fn(a.type,a.props,a.key,a.ref?a.ref:null,a.__v):a).__=e,a.__b=e.__b+1,s=null,(l=a.__i=yl(a,r,c,p))!=-1&&(p--,(s=r[l])&&(s.__u|=2)),s==null||s.__v==null?(l==-1&&(o>u?d--:oc?d--:d++,a.__u|=4))):e.__k[i]=null;if(p)for(i=0;i(u?1:0)){for(o=r-1,i=r+1;o>=0||i=0?o--:i++])!=null&&!(2&l.__u)&&s==l.key&&c==l.type)return a}return-1}function Ri(e,t,r){t[0]=="-"?e.setProperty(t,r!=null?r:""):e[t]=r==null?"":typeof r!="number"||gl.test(t)?r:r+"px"}function un(e,t,r,n,o){var i,a;e:if(t=="style")if(typeof r=="string")e.style.cssText=r;else{if(typeof n=="string"&&(e.style.cssText=n=""),n)for(t in n)r&&t in r||Ri(e.style,t,"");if(r)for(t in r)n&&r[t]==n[t]||Ri(e.style,t,r[t])}else if(t[0]=="o"&&t[1]=="n")i=t!=(t=t.replace(da,"$1")),a=t.toLowerCase(),t=a in e||t=="onFocusOut"||t=="onFocusIn"?a.slice(2):t.slice(2),e.l||(e.l={}),e.l[t+i]=r,r?n?r.u=n.u:(r.u=lo,e.addEventListener(t,i?ro:to,i)):e.removeEventListener(t,i?ro:to,i);else{if(o=="http://www.w3.org/2000/svg")t=t.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(t!="width"&&t!="height"&&t!="href"&&t!="list"&&t!="form"&&t!="tabIndex"&&t!="download"&&t!="rowSpan"&&t!="colSpan"&&t!="role"&&t!="popover"&&t in e)try{e[t]=r!=null?r:"";break e}catch(s){}typeof r=="function"||(r==null||r===!1&&t[4]!="-"?e.removeAttribute(t):e.setAttribute(t,t=="popover"&&r==1?"":r))}}function ji(e){return function(t){if(this.l){var r=this.l[t.type+e];if(t.t==null)t.t=lo++;else if(t.t0?e:Pr(e)?e.map(ya):pt({},e)}function xl(e,t,r,n,o,i,a,s,c){var l,u,p,d,m,h,v,x=r.props,w=t.props,E=t.type;if(E=="svg"?o="http://www.w3.org/2000/svg":E=="math"?o="http://www.w3.org/1998/Math/MathML":o||(o="http://www.w3.org/1999/xhtml"),i!=null){for(l=0;l=r.__.length&&r.__.push({}),r.__[e]}function bn(e){return $r=1,Tl(Ta,e)}function Tl(e,t,r){var n=mo(Hr++,2);if(n.t=e,!n.__c&&(n.__=[r?r(t):Ta(void 0,t),function(s){var c=n.__N?n.__N[0]:n.__[0],l=n.t(c,s);c!==l&&(n.__N=[l,n.__[1]],n.__c.setState({}))}],n.__c=ve,!ve.__f)){var o=function(s,c,l){if(!n.__c.__H)return!0;var u=n.__c.__H.__.filter(function(d){return!!d.__c});if(u.every(function(d){return!d.__N}))return!i||i.call(this,s,c,l);var p=n.__c.props!==s;return u.forEach(function(d){if(d.__N){var m=d.__[0];d.__=d.__N,d.__N=void 0,m!==d.__[0]&&(p=!0)}}),i&&i.call(this,s,c,l)||p};ve.__f=!0;var i=ve.shouldComponentUpdate,a=ve.componentWillUpdate;ve.componentWillUpdate=function(s,c,l){if(this.__e){var u=i;i=void 0,o(s,c,l),i=u}a&&a.call(this,s,c,l)},ve.shouldComponentUpdate=o}return n.__N||n.__}function mt(e,t){var r=mo(Hr++,3);!we.__s&&Ea(r.__H,t)&&(r.__=e,r.u=t,ve.__H.__h.push(r))}function Vt(e){return $r=5,ur(function(){return{current:e}},[])}function ur(e,t){var r=mo(Hr++,7);return Ea(r.__H,t)&&(r.__=e(),r.__H=t,r.__h=e),r.__}function Sl(e,t){return $r=8,ur(function(){return e},t)}function Ol(){for(var e;e=wa.shift();)if(e.__P&&e.__H)try{e.__H.__h.forEach(mn),e.__H.__h.forEach(oo),e.__H.__h=[]}catch(t){e.__H.__h=[],we.__e(t,e.__v)}}we.__b=function(e){ve=null,Ui&&Ui(e)},we.__=function(e,t){e&&t.__k&&t.__k.__m&&(e.__m=t.__k.__m),zi&&zi(e,t)},we.__r=function(e){Ni&&Ni(e),Hr=0;var t=(ve=e.__c).__H;t&&(Zn===ve?(t.__h=[],ve.__h=[],t.__.forEach(function(r){r.__N&&(r.__=r.__N),r.u=r.__N=void 0})):(t.__h.forEach(mn),t.__h.forEach(oo),t.__h=[],Hr=0)),Zn=ve},we.diffed=function(e){Di&&Di(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&(wa.push(t)!==1&&Fi===we.requestAnimationFrame||((Fi=we.requestAnimationFrame)||Ll)(Ol)),t.__H.__.forEach(function(r){r.u&&(r.__H=r.u),r.u=void 0})),Zn=ve=null},we.__c=function(e,t){t.some(function(r){try{r.__h.forEach(mn),r.__h=r.__h.filter(function(n){return!n.__||oo(n)})}catch(n){t.some(function(o){o.__h&&(o.__h=[])}),t=[],we.__e(n,r.__v)}}),Wi&&Wi(e,t)},we.unmount=function(e){Vi&&Vi(e);var t,r=e.__c;r&&r.__H&&(r.__H.__.forEach(function(n){try{mn(n)}catch(o){t=o}}),r.__H=void 0,t&&we.__e(t,r.__v))};var qi=typeof requestAnimationFrame=="function";function Ll(e){var t,r=function(){clearTimeout(n),qi&&cancelAnimationFrame(t),setTimeout(e)},n=setTimeout(r,35);qi&&(t=requestAnimationFrame(r))}function mn(e){var t=ve,r=e.__c;typeof r=="function"&&(e.__c=void 0,r()),ve=t}function oo(e){var t=ve;e.__c=e.__(),ve=t}function Ea(e,t){return!e||e.length!==t.length||t.some(function(r,n){return r!==e[n]})}function Ta(e,t){return typeof t=="function"?t(e):t}function Ml(e,t){for(var r in t)e[r]=t[r];return e}function Ki(e,t){for(var r in e)if(r!=="__source"&&!(r in t))return!0;for(var n in t)if(n!=="__source"&&e[n]!==t[n])return!0;return!1}function Bi(e,t){this.props=e,this.context=t}(Bi.prototype=new at).isPureReactComponent=!0,Bi.prototype.shouldComponentUpdate=function(e,t){return Ki(this.props,e)||Ki(this.state,t)};var Yi=K.__b;K.__b=function(e){e.type&&e.type.__f&&e.ref&&(e.props.ref=e.ref,e.ref=null),Yi&&Yi(e)};var Yx=typeof Symbol<"u"&&Symbol.for&&Symbol.for("react.forward_ref")||3911,kl=K.__e;K.__e=function(e,t,r,n){if(e.then){for(var o,i=t;i=i.__;)if((o=i.__c)&&o.__c)return t.__e==null&&(t.__e=r.__e,t.__k=r.__k),o.__c(e,t)}kl(e,t,r,n)};var Gi=K.unmount;function Sa(e,t,r){return e&&(e.__c&&e.__c.__H&&(e.__c.__H.__.forEach(function(n){typeof n.__c=="function"&&n.__c()}),e.__c.__H=null),(e=Ml({},e)).__c!=null&&(e.__c.__P===r&&(e.__c.__P=t),e.__c.__e=!0,e.__c=null),e.__k=e.__k&&e.__k.map(function(n){return Sa(n,t,r)})),e}function Oa(e,t,r){return e&&r&&(e.__v=null,e.__k=e.__k&&e.__k.map(function(n){return Oa(n,t,r)}),e.__c&&e.__c.__P===t&&(e.__e&&r.appendChild(e.__e),e.__c.__e=!0,e.__c.__P=r)),e}function Qn(){this.__u=0,this.o=null,this.__b=null}function La(e){var t=e.__.__c;return t&&t.__a&&t.__a(e)}function pn(){this.i=null,this.l=null}K.unmount=function(e){var t=e.__c;t&&t.__R&&t.__R(),t&&32&e.__u&&(e.type=null),Gi&&Gi(e)},(Qn.prototype=new at).__c=function(e,t){var r=t.__c,n=this;n.o==null&&(n.o=[]),n.o.push(r);var o=La(n.__v),i=!1,a=function(){i||(i=!0,r.__R=null,o?o(s):s())};r.__R=a;var s=function(){if(!--n.__u){if(n.state.__a){var c=n.state.__a;n.__v.__k[0]=Oa(c,c.__c.__P,c.__c.__O)}var l;for(n.setState({__a:n.__b=null});l=n.o.pop();)l.forceUpdate()}};n.__u++||32&t.__u||n.setState({__a:n.__b=n.__v.__k[0]}),e.then(a,a)},Qn.prototype.componentWillUnmount=function(){this.o=[]},Qn.prototype.render=function(e,t){if(this.__b){if(this.__v.__k){var r=document.createElement("div"),n=this.__v.__k[0].__c;this.__v.__k[0]=Sa(this.__b,r,n.__O=n.__P)}this.__b=null}var o=t.__a&&Wt(ft,null,e.fallback);return o&&(o.__u&=-33),[Wt(ft,null,t.__a?null:e.children),o]};var Ji=function(e,t,r){if(++r[1]===r[0]&&e.l.delete(t),e.props.revealOrder&&(e.props.revealOrder[0]!=="t"||!e.l.size))for(r=e.i;r;){for(;r.length>3;)r.pop()();if(r[1]Object.freeze({get current(){return t.current}}),[])}var Nl=typeof globalThis<"u"&&typeof navigator<"u"&&typeof document<"u";function Dl(e,...t){var r;(r=e==null?void 0:e.addEventListener)==null||r.call(e,...t)}function Wl(e,...t){var r;(r=e==null?void 0:e.removeEventListener)==null||r.call(e,...t)}var Vl=(e,t)=>Object.hasOwn(e,t),zl=()=>!0,ql=()=>!1;function Kl(e=!1){let t=Vt(e),r=Sl(()=>t.current,[]);return mt(()=>(t.current=!0,()=>{t.current=!1}),[]),r}function Bl(e,...t){let r=Kl(),n=ka(t[1]),o=ur(()=>function(...i){r()&&(typeof n.current=="function"?n.current.apply(this,i):typeof n.current.handleEvent=="function"&&n.current.handleEvent.apply(this,i))},[]);mt(()=>{let i=Yl(e)?e.current:e;if(!i)return;let a=t.slice(2);return Dl(i,t[0],o,...a),()=>{Wl(i,t[0],o,...a)}},[e,t[0]])}function Yl(e){return e!==null&&typeof e=="object"&&Vl(e,"current")}var Gl=e=>typeof e=="function"?e:typeof e=="string"?t=>t.key===e:e?zl:ql,Jl=Nl?globalThis:null;function Aa(e,t,r=[],n={}){let{event:o="keydown",target:i=Jl,eventOptions:a}=n,s=ka(t),c=ur(()=>{let l=Gl(e);return function(u){l(u)&&s.current.call(this,u)}},r);Bl(i,o,c,a)}function Ca(e){var t,r,n="";if(typeof e=="string"||typeof e=="number")n+=e;else if(typeof e=="object")if(Array.isArray(e)){var o=e.length;for(t=0;t1)St--;else{for(var e,t=!1;kr!==void 0;){var r=kr;for(kr=void 0,io++;r!==void 0;){var n=r.o;if(r.o=void 0,r.f&=-3,!(8&r.f)&&Pa(r))try{r.c()}catch(o){t||(e=o,t=!0)}r=n}}if(io=0,St--,t)throw e}}function Ql(e){if(St>0)return e();St++;try{return e()}finally{xn()}}var ae=void 0;function Ha(e){var t=ae;ae=void 0;try{return e()}finally{ae=t}}var kr=void 0,St=0,io=0,gn=0;function $a(e){if(ae!==void 0){var t=e.n;if(t===void 0||t.t!==ae)return t={i:0,S:e,p:ae.s,n:void 0,t:ae,e:void 0,x:void 0,r:t},ae.s!==void 0&&(ae.s.n=t),ae.s=t,e.n=t,32&ae.f&&e.S(t),t;if(t.i===-1)return t.i=0,t.n!==void 0&&(t.n.p=t.p,t.p!==void 0&&(t.p.n=t.n),t.p=ae.s,t.n=void 0,ae.s.n=t,ae.s=t),t}}function Ce(e,t){this.v=e,this.i=0,this.n=void 0,this.t=void 0,this.W=t==null?void 0:t.watched,this.Z=t==null?void 0:t.unwatched,this.name=t==null?void 0:t.name}Ce.prototype.brand=Zl;Ce.prototype.h=function(){return!0};Ce.prototype.S=function(e){var t=this,r=this.t;r!==e&&e.e===void 0&&(e.x=r,this.t=e,r!==void 0?r.e=e:Ha(function(){var n;(n=t.W)==null||n.call(t)}))};Ce.prototype.U=function(e){var t=this;if(this.t!==void 0){var r=e.e,n=e.x;r!==void 0&&(r.x=n,e.e=void 0),n!==void 0&&(n.e=r,e.x=void 0),e===this.t&&(this.t=n,n===void 0&&Ha(function(){var o;(o=t.Z)==null||o.call(t)}))}};Ce.prototype.subscribe=function(e){var t=this;return qt(function(){var r=t.value,n=ae;ae=void 0;try{e(r)}finally{ae=n}},{name:"sub"})};Ce.prototype.valueOf=function(){return this.value};Ce.prototype.toString=function(){return this.value+""};Ce.prototype.toJSON=function(){return this.value};Ce.prototype.peek=function(){var e=ae;ae=void 0;try{return this.value}finally{ae=e}};Object.defineProperty(Ce.prototype,"value",{get:function(){var e=$a(this);return e!==void 0&&(e.i=this.i),this.v},set:function(e){if(e!==this.v){if(io>100)throw new Error("Cycle detected");this.v=e,this.i++,gn++,St++;try{for(var t=this.t;t!==void 0;t=t.x)t.t.N()}finally{xn()}}}});function Ot(e,t){return new Ce(e,t)}function Pa(e){for(var t=e.s;t!==void 0;t=t.n)if(t.S.i!==t.i||!t.S.h()||t.S.i!==t.i)return!0;return!1}function Ia(e){for(var t=e.s;t!==void 0;t=t.n){var r=t.S.n;if(r!==void 0&&(t.r=r),t.S.n=t,t.i=-1,t.n===void 0){e.s=t;break}}}function Ra(e){for(var t=e.s,r=void 0;t!==void 0;){var n=t.p;t.i===-1?(t.S.U(t),n!==void 0&&(n.n=t.n),t.n!==void 0&&(t.n.p=n)):r=t,t.S.n=t.r,t.r!==void 0&&(t.r=void 0),t=n}e.s=r}function Kt(e,t){Ce.call(this,void 0),this.x=e,this.s=void 0,this.g=gn-1,this.f=4,this.W=t==null?void 0:t.watched,this.Z=t==null?void 0:t.unwatched,this.name=t==null?void 0:t.name}Kt.prototype=new Ce;Kt.prototype.h=function(){if(this.f&=-3,1&this.f)return!1;if((36&this.f)==32||(this.f&=-5,this.g===gn))return!0;if(this.g=gn,this.f|=1,this.i>0&&!Pa(this))return this.f&=-2,!0;var e=ae;try{Ia(this),ae=this;var t=this.x();(16&this.f||this.v!==t||this.i===0)&&(this.v=t,this.f&=-17,this.i++)}catch(r){this.v=r,this.f|=16,this.i++}return ae=e,Ra(this),this.f&=-2,!0};Kt.prototype.S=function(e){if(this.t===void 0){this.f|=36;for(var t=this.s;t!==void 0;t=t.n)t.S.S(t)}Ce.prototype.S.call(this,e)};Kt.prototype.U=function(e){if(this.t!==void 0&&(Ce.prototype.U.call(this,e),this.t===void 0)){this.f&=-33;for(var t=this.s;t!==void 0;t=t.n)t.S.U(t)}};Kt.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(var e=this.t;e!==void 0;e=e.x)e.t.N()}};Object.defineProperty(Kt.prototype,"value",{get:function(){if(1&this.f)throw new Error("Cycle detected");var e=$a(this);if(this.h(),e!==void 0&&(e.i=this.i),16&this.f)throw this.v;return this.v}});function ta(e,t){return new Kt(e,t)}function ja(e){var t=e.u;if(e.u=void 0,typeof t=="function"){St++;var r=ae;ae=void 0;try{t()}catch(n){throw e.f&=-2,e.f|=8,ho(e),n}finally{ae=r,xn()}}}function ho(e){for(var t=e.s;t!==void 0;t=t.n)t.S.U(t);e.x=void 0,e.s=void 0,ja(e)}function eu(e){if(ae!==this)throw new Error("Out-of-order effect");Ra(this),ae=e,this.f&=-2,8&this.f&&ho(this),xn()}function pr(e,t){this.x=e,this.u=void 0,this.s=void 0,this.o=void 0,this.f=32,this.name=t==null?void 0:t.name}pr.prototype.c=function(){var e=this.S();try{if(8&this.f||this.x===void 0)return;var t=this.x();typeof t=="function"&&(this.u=t)}finally{e()}};pr.prototype.S=function(){if(1&this.f)throw new Error("Cycle detected");this.f|=1,this.f&=-9,ja(this),Ia(this),St++;var e=ae;return ae=this,eu.bind(this,e)};pr.prototype.N=function(){2&this.f||(this.f|=2,this.o=kr,kr=this)};pr.prototype.d=function(){this.f|=8,1&this.f||ho(this)};pr.prototype.dispose=function(){this.d()};function qt(e,t){var r=new pr(e,t);try{r.c()}catch(o){throw r.d(),o}var n=r.d.bind(r);return n[Symbol.dispose]=n,n}var Fa,vo,eo,Ua=[];qt(function(){Fa=this.N})();function fr(e,t){K[e]=t.bind(null,K[e]||function(){})}function _n(e){eo&&eo(),eo=e&&e.S()}function Na(e){var t=this,r=e.data,n=ru(r);n.value=r;var o=ur(function(){for(var s=t,c=t.__v;c=c.__;)if(c.__c){c.__c.__$f|=4;break}var l=ta(function(){var m=n.value.value;return m===0?0:m===!0?"":m||""}),u=ta(function(){return!Array.isArray(l.value)&&!pa(l.value)}),p=qt(function(){if(this.N=Da,u.value){var m=l.value;s.__v&&s.__v.__e&&s.__v.__e.nodeType===3&&(s.__v.__e.data=m)}}),d=t.__$u.d;return t.__$u.d=function(){p(),d.call(this)},[u,l]},[]),i=o[0],a=o[1];return i.value?a.peek():a.value}Na.displayName="ReactiveTextNode";Object.defineProperties(Ce.prototype,{constructor:{configurable:!0,value:void 0},type:{configurable:!0,value:Na},props:{configurable:!0,get:function(){return{data:this}}},__b:{configurable:!0,value:1}});fr("__b",function(e,t){if(typeof t.type=="function"&&typeof window<"u"&&window.__PREACT_SIGNALS_DEVTOOLS__&&window.__PREACT_SIGNALS_DEVTOOLS__.exitComponent(),typeof t.type=="string"){var r,n=t.props;for(var o in n)if(o!=="children"){var i=n[o];i instanceof Ce&&(r||(t.__np=r={}),r[o]=i,n[o]=i.peek())}}e(t)});fr("__r",function(e,t){if(typeof t.type=="function"&&typeof window<"u"&&window.__PREACT_SIGNALS_DEVTOOLS__&&window.__PREACT_SIGNALS_DEVTOOLS__.enterComponent(t),t.type!==ft){_n();var r,n=t.__c;n&&(n.__$f&=-2,(r=n.__$u)===void 0&&(n.__$u=r=(function(o){var i;return qt(function(){i=this}),i.c=function(){n.__$f|=1,n.setState({})},i})())),vo=n,_n(r)}e(t)});fr("__e",function(e,t,r,n){typeof window<"u"&&window.__PREACT_SIGNALS_DEVTOOLS__&&window.__PREACT_SIGNALS_DEVTOOLS__.exitComponent(),_n(),vo=void 0,e(t,r,n)});fr("diffed",function(e,t){typeof t.type=="function"&&typeof window<"u"&&window.__PREACT_SIGNALS_DEVTOOLS__&&window.__PREACT_SIGNALS_DEVTOOLS__.exitComponent(),_n(),vo=void 0;var r;if(typeof t.type=="string"&&(r=t.__e)){var n=t.__np,o=t.props;if(n){var i=r.U;if(i)for(var a in i){var s=i[a];s!==void 0&&!(a in n)&&(s.d(),i[a]=void 0)}else i={},r.U=i;for(var c in n){var l=i[c],u=n[c];l===void 0?(l=tu(r,c,u,o),i[c]=l):l.o(u,o)}}}e(t)});function tu(e,t,r,n){var o=t in e&&e.ownerSVGElement===void 0,i=Ot(r);return{o:function(a,s){i.value=a,n=s},d:qt(function(){this.N=Da;var a=i.value.value;n[t]!==a&&(n[t]=a,o?e[t]=a:a?e.setAttribute(t,a):e.removeAttribute(t))})}}fr("unmount",function(e,t){if(typeof t.type=="string"){var r=t.__e;if(r){var n=r.U;if(n){r.U=void 0;for(var o in n){var i=n[o];i&&i.d()}}}}else{var a=t.__c;if(a){var s=a.__$u;s&&(a.__$u=void 0,s.d())}}e(t)});fr("__h",function(e,t,r,n){(n<3||n===9)&&(t.__$f|=2),e(t,r,n)});at.prototype.shouldComponentUpdate=function(e,t){var r=this.__$u,n=r&&r.s!==void 0;for(var o in t)return!0;if(this.__f||typeof this.u=="boolean"&&this.u===!0){var i=2&this.__$f;if(!(n||i||4&this.__$f)||1&this.__$f)return!0}else if(!(n||4&this.__$f)||3&this.__$f)return!0;for(var a in e)if(a!=="__source"&&e[a]!==this.props[a])return!0;for(var s in this.props)if(!(s in e))return!0;return!1};function ru(e,t){return bn(function(){return Ot(e,t)})[0]}var nu=function(e){queueMicrotask(function(){queueMicrotask(e)})};function ou(){Ql(function(){for(var e;e=Ua.shift();)Fa.call(e)})}function Da(){Ua.push(this)===1&&(K.requestAnimationFrame||nu)(ou)}var ao=[0];for(let e=0;e<32;e++)ao.push(ao[e]|1<>>5]>>>e&1}set(e){this.data[e>>>5]|=1<<(e&31)}forEach(e){let t=this.size&31;for(let r=0;r{var r;return(r=t.tags)==null?void 0:r.length})&&(matchMedia("(max-width: 768px)").matches||Wa())}function Dt(){Qe.value=He(H({},Qe.value),{hideSearch:!Qe.value.hideSearch})}function Wa(){Qe.value=He(H({},Qe.value),{hideFilters:!Qe.value.hideFilters})}function dn(){return Qe.value.selectedItem}function so(e){Qe.value=He(H({},Qe.value),{selectedItem:e})}function su(){var e,t;return(t=(e=lr.value)==null?void 0:e.items)!=null?t:[]}function wn(){return typeof Se.value.input=="string"?Se.value.input:""}function Va(e){let t=za();e.length&&!t.length?Se.value=He(H({},Se.value),{page:void 0,input:e}):!e.length&&t.length?Se.value=He(H({},Se.value),{page:void 0,input:{type:"operator",data:{operator:"not",operands:[]}}}):Se.value=He(H({},Se.value),{page:void 0,input:e})}function cu(){typeof it.value.pagination.next<"u"&&(Se.value=He(H({},Se.value),{page:it.value.pagination.next}))}function lu(e){let t=Se.value.filter.input;if("type"in t&&t.type==="operator"){for(let r of t.data.operands)if("type"in r&&r.type==="value"&&typeof r.data.value=="string"&&r.data.value===e)return!0}return!1}function za(){let e=Se.value.filter.input,t=[];if("type"in e&&e.type==="operator")for(let r of e.data.operands)"type"in r&&r.type==="value"&&typeof r.data.value=="string"&&t.push(r.data.value);return t}function uu(e){let t=Se.value.filter.input,r=[];if("type"in t&&t.type==="operator")for(let n of t.data.operands)"type"in n&&n.type==="value"&&typeof n.data.value=="string"&&r.push(n.data.value);if(r.includes(e)){let n=r.indexOf(e);n>-1&&r.splice(n,1)}else r.push(e);Se.value=He(H({},Se.value),{page:void 0,filter:He(H({},Se.value.filter),{input:{type:"operator",data:{operator:"and",operands:r.map(n=>({type:"value",data:{field:"tags",value:n}}))}}})}),Va(wn())}function pu(){return it.value.items}function fu(){return it.value.total}function mu(){var e;for(let t of(e=it.value.aggregations)!=null?e:[])if(t.type==="term")return t.data.value;return[]}function sr(){return Qe.value.hideSearch}function du(){return Qe.value.hideFilters}function qa(){var e;return(e=Ka.value.highlight)!=null?e:!1}var Qe=Ot({hideSearch:!0,hideFilters:!0,selectedItem:0}),Ka=Ot({}),lr=Ot(),na=Ot(),Se=Ot({input:"",filter:{input:{type:"operator",data:{operator:"and",operands:[]}},aggregation:{input:[{type:"term",data:{field:"tags"}}]}}}),it=Ot({items:[],query:{select:{documents:new ra(0),terms:new ra(0)},values:[]},pagination:{total:0}});function hu(e,t,r){for(let n=0;tr&&t(0,o,r,r=i);continue;case 62:e.charCodeAt(r+1)===47?t(2,--o,r,r=i+1):hu(e,r,n)?t(3,o,r,r=i+1):t(1,o++,r,r=i+1)}i>r&&t(0,o,r,i)}function bu(e,t=0,r=e.length){let n=++t;e:for(let l=0;n{let i=[],a=[],{onElement:s,onText:c=gu}=typeof r=="function"?{onElement:r}:r,l=0,u=0;return e(t,(p,d,m,h)=>{if(p===0)i[l++]=c(t,m,h),a[u++]={value:null,depth:d};else if(p&1&&(a[u++]={value:bu(t,m,h),depth:d}),p&2)for(let v=0;u>=0;v++){let{value:x,depth:w}=a[--u];if(w>d)continue;let E=i.slice(l-=v,l+v);i[l++]=s(x,E),u++;break}},n,o),i.slice(0,l)}}function yu(e){return e.replace(/[&<>]/g,t=>{switch(t.charCodeAt(0)){case 38:return"&";case 60:return"<";case 62:return">"}})}function hn(e){return e.replace(/&(amp|[lg]t);/g,t=>{switch(t.charCodeAt(1)){case 97:return"&";case 108:return"<";case 103:return">"}})}function xu(e,t){return{start:e.start+t,end:e.end+t,value:e.value}}function wu(e,t,r){return e.slice(t,r)}function Eu(e){let{onHighlight:t,onText:r=wu}=typeof e=="function"?{onHighlight:e}:e;return(n,o,i=0,a=n.length)=>{var l;let s=[],c=(l=o==null?void 0:o.ranges)!=null?l:[];for(let u=0,p=i;ua)break;let m=c[u].end;if(mi&&s.push(r(n,i,d));let{value:h}=c[u];s.push(t(n,{start:d,end:i=m,value:h}))}return i{let o=n.data;switch(o.type){case 1:na.value=!0;break;case 3:typeof o.data.pagination.prev<"u"?it.value=He(H({},it.value),{pagination:o.data.pagination,items:[...it.value.items,...o.data.items]}):(it.value=o.data,so(0));break}},qt(()=>{lr.value&&r.postMessage({type:0,data:lr.value})}),qt(()=>{na.value&&r.postMessage({type:2,data:Se.value})})}var oa={container:"p",hidden:"m"};function ku(e){return z("div",{class:zt(oa.container,{[oa.hidden]:e.hidden}),onClick:()=>Dt()})}var ia={container:"r",disabled:"c"};function co(e){return z("button",{class:zt(ia.container,{[ia.disabled]:!e.onClick}),onClick:e.onClick,children:e.children})}var aa=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),Au=e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(t,r,n)=>n?n.toUpperCase():r.toLowerCase()),sa=e=>{let t=Au(e);return t.charAt(0).toUpperCase()+t.slice(1)},Cu=(...e)=>e.filter((t,r,n)=>!!t&&t.trim()!==""&&n.indexOf(t)===r).join(" ").trim(),Hu={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},$u=c=>{var l=c,{color:e="currentColor",size:t=24,strokeWidth:r=2,absoluteStrokeWidth:n,children:o,iconNode:i,class:a=""}=l,s=gr(l,["color","size","strokeWidth","absoluteStrokeWidth","children","iconNode","class"]);return Wt("svg",H(He(H({},Hu),{width:String(t),height:t,stroke:e,"stroke-width":n?Number(r)*24/Number(t):r,class:["lucide",a].join(" ")}),s),[...i.map(([u,p])=>Wt(u,p)),...Cr(o)])},bo=(e,t)=>{let r=a=>{var s=a,{class:n="",children:o}=s,i=gr(s,["class","children"]);return Wt($u,He(H({},i),{iconNode:t,class:Cu(`lucide-${aa(sa(e))}`,`lucide-${aa(e)}`,n)}),o)};return r.displayName=sa(e),r},Pu=bo("corner-down-left",[["path",{d:"M20 4v7a4 4 0 0 1-4 4H4",key:"6o5b7l"}],["path",{d:"m9 10-5 5 5 5",key:"1kshq7"}]]),Iu=bo("list-filter",[["path",{d:"M2 5h20",key:"1fs1ex"}],["path",{d:"M6 12h12",key:"8npq4p"}],["path",{d:"M9 19h6",key:"456am0"}]]),Ru=bo("search",[["path",{d:"m21 21-4.34-4.34",key:"14j7rj"}],["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}]]),Gx=hl(vl(),1);function ju({threshold:e=0,root:t=null,rootMargin:r="0%",freezeOnceVisible:n=!1,initialIsIntersecting:o=!1,onChange:i}={}){var a;let[s,c]=bn(null),[l,u]=bn(()=>({isIntersecting:o,entry:void 0})),p=Vt();p.current=i;let d=((a=l.entry)==null?void 0:a.isIntersecting)&&n;mt(()=>{if(!s||!("IntersectionObserver"in window)||d)return;let v,x=new IntersectionObserver(w=>{let E=Array.isArray(x.thresholds)?x.thresholds:[x.thresholds];w.forEach(_=>{let de=_.isIntersecting&&E.some(be=>_.intersectionRatio>=be);u({isIntersecting:de,entry:_}),p.current&&p.current(de,_),de&&n&&v&&(v(),v=void 0)})},{threshold:e,root:t,rootMargin:r});return x.observe(s),()=>{x.disconnect()}},[s,JSON.stringify(e),t,r,d,n]);let m=Vt(null);mt(()=>{var v;!s&&(v=l.entry)!=null&&v.target&&!n&&!d&&m.current!==l.entry.target&&(m.current=l.entry.target,u({isIntersecting:o,entry:void 0}))},[s,l.entry,n,d,o]);let h=[c,!!l.isIntersecting,l.entry];return h.ref=h[0],h.isIntersecting=h[1],h.entry=h[2],h}var lt={container:"n",hidden:"l",content:"u",pop:"d",badge:"y",sidebar:"i",controls:"w",results:"k",loadmore:"z"};function Fu(e){let{isIntersecting:t,ref:r}=ju({threshold:0});mt(()=>{t&&cu()},[t]);let n=Vt(null);mt(()=>{n.current&&typeof Se.value.page>"u"&&n.current.scrollTo({top:0,behavior:"smooth"})},[Se.value]);let o=za();return z("div",{class:zt(lt.container,{[lt.hidden]:e.hidden}),children:[z("div",{class:lt.content,children:[z("div",{class:lt.controls,children:[z(co,{onClick:Dt,children:z(Ru,{})}),z(Nu,{focus:!e.hidden}),z(co,{onClick:Wa,children:[z(Iu,{}),o.length>0&&z("span",{class:lt.badge,children:o.length})]})]}),z("div",{class:lt.results,ref:n,children:[z(Du,{keyboard:!e.hidden}),z("div",{class:lt.loadmore,ref:r})]})]}),z("div",{class:zt(lt.sidebar,{[lt.hidden]:du()}),children:z(Uu,{})})]})}var Tt={container:"X",list:"j",heading:"F",title:"I",item:"o",active:"g",value:"R",count:"q"};function Uu(e){let t=mu();return t.sort((r,n)=>n.node.count-r.node.count),z("div",{class:Tt.container,children:[z("h3",{class:Tt.heading,children:"Filters"}),z("h4",{class:Tt.title,children:"Tags"}),z("ol",{class:Tt.list,children:t.map(r=>z("li",{class:zt(Tt.item,{[Tt.active]:lu(r.node.value)}),onClick:()=>uu(r.node.value),children:[z("span",{class:Tt.value,children:r.node.value}),z("span",{class:Tt.count,children:r.node.count})]}))})]})}var ca={container:"f"};function Nu(e){let t=Vt(null);return mt(()=>{var r,n;e.focus?(r=t.current)==null||r.focus():(n=t.current)==null||n.blur()},[e.focus]),z("div",{class:ca.container,children:z("input",{ref:t,type:"text",class:ca.content,value:hn(wn()),onInput:r=>Va(yu(r.currentTarget.value)),autocapitalize:"off",autocomplete:"off",autocorrect:"off",placeholder:"Search",spellcheck:!1,role:"combobox"})})}var ut={container:"b",heading:"A",item:"a",active:"h",wrapper:"B",actions:"s",title:"x",path:"t"};function Ga(){let[e,t]=bn(!1);return mt(()=>{let r=()=>t(!0),n=()=>t(!1);return document.addEventListener("compositionstart",r),document.addEventListener("compositionend",n),()=>{document.removeEventListener("compositionstart",r),document.removeEventListener("compositionend",n)}},[]),e}function Du(e){var s;let t=su(),r=pu(),n=dn(),o=Vt([]),i=Ga();mt(()=>{let c=o.current[n];c&&c.scrollIntoView({block:"center",behavior:"smooth"})},[n]),Aa(e.keyboard,c=>{if(i)return;let l=dn();c.key==="ArrowDown"?(c.preventDefault(),so(Math.min(l+1,r.length-1))):c.key==="ArrowUp"&&(c.preventDefault(),so(Math.max(l-1,0)))},[e.keyboard,i]);let a=(s=fu())!=null?s:0;return z(ft,{children:[r.length>0&&z("h3",{class:ut.heading,children:[z("span",{class:ut.bubble,children:new Intl.NumberFormat("en-US").format(a)})," ","results"]}),z("ol",{class:ut.container,children:r.map((c,l)=>{var m;let u=Ba(t[c.id].title,c.matches.find(({field:h})=>h==="title")),p=Mu((m=t[c.id].path)!=null?m:[],c.matches.find(({field:h})=>h==="path")),d=t[c.id].location;if(qa()){let h=encodeURIComponent(wn()),[v,x]=d.split("#",2);d=`${v}?h=${h.replace(/%20/g,"+")}`,typeof x<"u"&&(d+=`#${x}`)}return z("li",{children:z("a",{ref:h=>{o.current[l]=h},href:d,onClick:()=>Dt(),class:zt(ut.item,{[ut.active]:l===dn()}),children:[z("div",{class:ut.wrapper,children:[z("h2",{class:ut.title,children:u}),z("menu",{class:ut.path,children:p.map(h=>z("li",{children:h}))})]}),z("nav",{class:ut.actions,children:z(co,{children:z(Pu,{})})})]})})})})]})}var Wu={container:"e"};function Vu(e){let t=Ga();return Aa(!0,r=>{var n,o,i,a,s;if(!t)if((r.metaKey||r.ctrlKey)&&r.key==="k")r.preventDefault(),Dt();else if((r.metaKey||r.ctrlKey)&&r.key==="j")document.body.classList.toggle("dark");else if(r.key==="Enter"&&!sr()){r.preventDefault();let c=dn(),l=(o=(n=it.value)==null?void 0:n.items[c])==null?void 0:o.id;if((a=(i=lr.value)==null?void 0:i.items[l])!=null&&a.location){Dt();let u=(s=lr.value)==null?void 0:s.items[l].location;if(qa()){let p=encodeURIComponent(wn()),[d,m]=u.split("#",2);u=`${d}?h=${p.replace(/%20/g,"+")}`,typeof m<"u"&&(u+=`#${m}`)}window.location.href=u}}else r.key==="Escape"&&!sr()&&(r.preventDefault(),Dt())},[t]),z("div",{class:Wu.container,children:[z(ku,{hidden:sr()}),z(Fu,{hidden:sr()})]})}function Ja(e,t){au(e),El(z(Vu,{}),t)}function go(){Dt()}function zu(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function qu(){return R(b(window,"compositionstart").pipe(f(()=>!0)),b(window,"compositionend").pipe(f(()=>!1))).pipe(J(!1))}function Xa(){let e=b(window,"keydown").pipe(f(t=>({mode:sr()?"global":"search",type:t.key,meta:t.ctrlKey||t.metaKey,claim(){t.preventDefault(),t.stopPropagation()}})),L(({mode:t,type:r})=>{if(t==="global"){let n=xt();if(typeof n!="undefined")return!zu(n,r)}return!0}),xe());return qu().pipe(g(t=>t?y:e))}function Ye(){return new URL(location.href)}function dt(e,t=!1){if(X("navigation.instant")&&!t){let r=A("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function Za(){return new I}function Qa(){return location.hash.slice(1)}function es(e){let t=A("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function _o(e){return R(b(window,"hashchange"),e).pipe(f(Qa),J(Qa()),L(t=>t.length>0),se(1))}function ts(e){return _o(e).pipe(f(t=>Le(`[id="${t}"]`)),L(t=>typeof t!="undefined"))}function Ir(e){let t=matchMedia(e);return an(r=>t.addListener(()=>r(t.matches))).pipe(J(t.matches))}function rs(){let e=matchMedia("print");return R(b(window,"beforeprint").pipe(f(()=>!0)),b(window,"afterprint").pipe(f(()=>!1))).pipe(J(e.matches))}function yo(e,t){return e.pipe(g(r=>r?t():y))}function xo(e,t){return new U(r=>{let n=new XMLHttpRequest;return n.open("GET",`${e}`),n.responseType="blob",n.addEventListener("load",()=>{n.status>=200&&n.status<300?(r.next(n.response),r.complete()):r.error(new Error(n.statusText))}),n.addEventListener("error",()=>{r.error(new Error("Network error"))}),n.addEventListener("abort",()=>{r.complete()}),typeof(t==null?void 0:t.progress$)!="undefined"&&(n.addEventListener("progress",o=>{var i;if(o.lengthComputable)t.progress$.next(o.loaded/o.total*100);else{let a=(i=n.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(o.loaded/+a*100)}}),t.progress$.next(5)),n.send(),()=>n.abort()})}function et(e,t){return xo(e,t).pipe(g(r=>r.text()),f(r=>JSON.parse(r)),se(1))}function En(e,t){let r=new DOMParser;return xo(e,t).pipe(g(n=>n.text()),f(n=>r.parseFromString(n,"text/html")),se(1))}function ns(e,t){let r=new DOMParser;return xo(e,t).pipe(g(n=>n.text()),f(n=>r.parseFromString(n,"text/xml")),se(1))}var wo={drawer:G("[data-md-toggle=drawer]"),search:G("[data-md-toggle=search]")};function Eo(e,t){wo[e].checked!==t&&wo[e].click()}function Tn(e){let t=wo[e];return b(t,"change").pipe(f(()=>t.checked),J(t.checked))}function os(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function is(){return R(b(window,"scroll",{passive:!0}),b(window,"resize",{passive:!0})).pipe(f(os),J(os()))}function as(){return{width:innerWidth,height:innerHeight}}function ss(){return b(window,"resize",{passive:!0}).pipe(f(as),J(as()))}function cs(){return re([is(),ss()]).pipe(f(([e,t])=>({offset:e,size:t})),se(1))}function Sn(e,{viewport$:t,header$:r}){let n=t.pipe(fe("size")),o=re([n,r]).pipe(f(()=>wt(e)));return re([r,t,o]).pipe(f(([{height:i},{offset:a,size:s},{x:c,y:l}])=>({offset:{x:a.x-c,y:a.y-l+i},size:s})))}var Ku=G("#__config"),mr=JSON.parse(Ku.textContent);mr.base=`${new URL(mr.base,Ye())}`;function Ue(){return mr}function X(e){return mr.features.includes(e)}function Bt(e,t){return typeof t!="undefined"?mr.translations[e].replace("#",t.toString()):mr.translations[e]}function ht(e,t=document){return G(`[data-md-component=${e}]`,t)}function Ee(e,t=document){return P(`[data-md-component=${e}]`,t)}function Bu(e){let t=G(".md-typeset > :first-child",e);return b(t,"click",{once:!0}).pipe(f(()=>G(".md-typeset",e)),f(r=>({hash:__md_hash(r.innerHTML)})))}function ls(e){if(!X("announce.dismiss")||!e.childElementCount)return y;if(!e.hidden){let t=G(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return j(()=>{let t=new I;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),Bu(e).pipe($(r=>t.next(r)),V(()=>t.complete()),f(r=>H({ref:e},r)))})}function Yu(e,{target$:t}){return t.pipe(f(r=>({hidden:r!==e})))}function us(e,t){let r=new I;return r.subscribe(({hidden:n})=>{e.hidden=n}),Yu(e,t).pipe($(n=>r.next(n)),V(()=>r.complete()),f(n=>H({ref:e},n)))}function To(e,t){return t==="inline"?A("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},A("div",{class:"md-tooltip__inner md-typeset"})):A("div",{class:"md-tooltip",id:e,role:"tooltip"},A("div",{class:"md-tooltip__inner md-typeset"}))}function On(...e){return A("div",{class:"md-tooltip2",role:"dialog"},A("div",{class:"md-tooltip2__inner md-typeset"},e))}function ps(...e){return A("div",{class:"md-tooltip2",role:"tooltip"},A("div",{class:"md-tooltip2__inner md-typeset"},e))}function fs(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return A("aside",{class:"md-annotation",tabIndex:0},To(t),A("a",{href:r,class:"md-annotation__index",tabIndex:-1},A("span",{"data-md-annotation-id":e})))}else return A("aside",{class:"md-annotation",tabIndex:0},To(t),A("span",{class:"md-annotation__index",tabIndex:-1},A("span",{"data-md-annotation-id":e})))}function ms(e){return A("button",{class:"md-code__button",title:Bt("clipboard.copy"),"data-clipboard-target":`#${e} > code`,"data-md-type":"copy"})}function ds(){return A("button",{class:"md-code__button",title:"Toggle line selection","data-md-type":"select"})}function hs(){return A("nav",{class:"md-code__nav"})}var Xu=_r(So());function bs(e){return A("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>A("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?Li(r):r)))}function Oo(e){let t=`tabbed-control tabbed-control--${e}`;return A("div",{class:t,hidden:!0},A("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function gs(e){return A("div",{class:"md-typeset__scrollwrap"},A("div",{class:"md-typeset__table"},e))}function Zu(e){var n;let t=Ue(),r=new URL(`../${e.version}/`,t.base);return A("li",{class:"md-version__item"},A("a",{href:`${r}`,class:"md-version__link"},e.title,((n=t.version)==null?void 0:n.alias)&&e.aliases.length>0&&A("span",{class:"md-version__alias"},e.aliases[0])))}function _s(e,t){var n;let r=Ue();return e=e.filter(o=>{var i;return!((i=o.properties)!=null&&i.hidden)}),A("div",{class:"md-version"},A("button",{class:"md-version__current","aria-label":Bt("select.version")},t.title,((n=r.version)==null?void 0:n.alias)&&t.aliases.length>0&&A("span",{class:"md-version__alias"},t.aliases[0])),A("ul",{class:"md-version__list"},e.map(Zu)))}var Qu=0;function ep(e,t=250){let r=re([ir(e),Ft(e,t)]).pipe(f(([o,i])=>o||i),ie()),n=j(()=>Ai(e)).pipe(oe(Ut),Lr(1),Ze(r),f(()=>Ci(e)));return r.pipe(Sr(o=>o),g(()=>re([r,n])),f(([o,i])=>({active:o,offset:i})),xe())}function Rr(e,t,r=250){let{content$:n,viewport$:o}=t,i=`__tooltip2_${Qu++}`;return j(()=>{let a=new I,s=new Un(!1);a.pipe(he(),ye(!1)).subscribe(s);let c=s.pipe(Tr(u=>Ve(+!u*250,Wn)),ie(),g(u=>u?n:y),$(u=>u.id=i),xe());re([a.pipe(f(({active:u})=>u)),c.pipe(g(u=>Ft(u,250)),J(!1))]).pipe(f(u=>u.some(p=>p))).subscribe(s);let l=s.pipe(L(u=>u),pe(c,o),f(([u,p,{size:d}])=>{let m=e.getBoundingClientRect(),h=m.width/2;if(p.role==="tooltip")return{x:h,y:8+m.height};if(m.y>=d.height/2){let{height:v}=Ae(p);return{x:h,y:-16-v}}else return{x:h,y:16+m.height}}));return re([c,a,l]).subscribe(([u,{offset:p},d])=>{u.style.setProperty("--md-tooltip-host-x",`${p.x}px`),u.style.setProperty("--md-tooltip-host-y",`${p.y}px`),u.style.setProperty("--md-tooltip-x",`${d.x}px`),u.style.setProperty("--md-tooltip-y",`${d.y}px`),u.classList.toggle("md-tooltip2--top",d.y<0),u.classList.toggle("md-tooltip2--bottom",d.y>=0)}),s.pipe(L(u=>u),pe(c,(u,p)=>p),L(u=>u.role==="tooltip")).subscribe(u=>{let p=Ae(G(":scope > *",u));u.style.setProperty("--md-tooltip-width",`${p.width}px`),u.style.setProperty("--md-tooltip-tail","0px")}),s.pipe(ie(),Ie(je),pe(c)).subscribe(([u,p])=>{p.classList.toggle("md-tooltip2--active",u)}),re([s.pipe(L(u=>u)),c]).subscribe(([u,p])=>{p.role==="dialog"?(e.setAttribute("aria-controls",i),e.setAttribute("aria-haspopup","dialog")):e.setAttribute("aria-describedby",i)}),s.pipe(L(u=>!u)).subscribe(()=>{e.removeAttribute("aria-controls"),e.removeAttribute("aria-describedby"),e.removeAttribute("aria-haspopup")}),ep(e,r).pipe($(u=>a.next(u)),V(()=>a.complete()),f(u=>H({ref:e},u)))})}function Ge(e,{viewport$:t},r=document.body){return Rr(e,{content$:new U(n=>{let o=e.title,i=ps(o);return n.next(i),e.removeAttribute("title"),r.append(i),()=>{i.remove(),e.setAttribute("title",o)}}),viewport$:t},0)}function tp(e,t){let r=j(()=>re([Hi(e),Ut(t)])).pipe(f(([{x:n,y:o},i])=>{let{width:a,height:s}=Ae(e);return{x:n-i.x+a/2,y:o-i.y+s/2}}));return ir(e).pipe(g(n=>r.pipe(f(o=>({active:n,offset:o})),Me(+!n||1/0))))}function ys(e,t,{target$:r}){let[n,o]=Array.from(e.children);return j(()=>{let i=new I,a=i.pipe(he(),ye(!0));return i.subscribe({next({offset:s}){e.style.setProperty("--md-tooltip-x",`${s.x}px`),e.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),Et(e).pipe(Q(a)).subscribe(s=>{e.toggleAttribute("data-md-visible",s)}),R(i.pipe(L(({active:s})=>s)),i.pipe(Be(250),L(({active:s})=>!s))).subscribe({next({active:s}){s?e.prepend(n):n.remove()},complete(){e.prepend(n)}}),i.pipe(Xe(16,je)).subscribe(({active:s})=>{n.classList.toggle("md-tooltip--active",s)}),i.pipe(Lr(125,je),L(()=>!!e.offsetParent),f(()=>e.offsetParent.getBoundingClientRect()),f(({x:s})=>s)).subscribe({next(s){s?e.style.setProperty("--md-tooltip-0",`${-s}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),b(o,"click").pipe(Q(a),L(s=>!(s.metaKey||s.ctrlKey))).subscribe(s=>{s.stopPropagation(),s.preventDefault()}),b(o,"mousedown").pipe(Q(a),pe(i)).subscribe(([s,{active:c}])=>{var l;if(s.button!==0||s.metaKey||s.ctrlKey)s.preventDefault();else if(c){s.preventDefault();let u=e.parentElement.closest(".md-annotation");u instanceof HTMLElement?u.focus():(l=xt())==null||l.blur()}}),r.pipe(Q(a),L(s=>s===n),It(125)).subscribe(()=>e.focus()),tp(e,t).pipe($(s=>i.next(s)),V(()=>i.complete()),f(s=>H({ref:e},s)))})}function rp(e){let t=Ue();if(e.tagName!=="CODE")return[e];let r=[".c",".c1",".cm"];if(t.annotate){let n=e.closest("[class|=language]");if(n)for(let o of Array.from(n.classList)){if(!o.startsWith("language-"))continue;let[,i]=o.split("-");i in t.annotate&&r.push(...t.annotate[i])}}return P(r.join(", "),e)}function np(e){let t=[];for(let r of rp(e)){let n=[],o=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=o.nextNode();i;i=o.nextNode())n.push(i);for(let i of n){let a;for(;a=/(\(\d+\))(!)?/.exec(i.textContent);){let[,s,c]=a;if(typeof c=="undefined"){let l=i.splitText(a.index);i=l.splitText(s.length),t.push(l)}else{i.textContent=s,t.push(i);break}}}}return t}function xs(e,t){t.append(...Array.from(e.childNodes))}function Ln(e,t,{target$:r,print$:n}){let o=t.closest("[id]"),i=o==null?void 0:o.id,a=new Map;for(let s of np(t)){let[,c]=s.textContent.match(/\((\d+)\)/);Le(`:scope > li:nth-child(${c})`,e)&&(a.set(c,fs(c,i)),s.replaceWith(a.get(c)))}return a.size===0?y:j(()=>{let s=new I,c=s.pipe(he(),ye(!0)),l=[];for(let[u,p]of a)l.push([G(".md-typeset",p),G(`:scope > li:nth-child(${u})`,e)]);return n.pipe(Q(c)).subscribe(u=>{e.hidden=!u,e.classList.toggle("md-annotation-list",u);for(let[p,d]of l)u?xs(p,d):xs(d,p)}),R(...[...a].map(([,u])=>ys(u,t,{target$:r}))).pipe(V(()=>s.complete()),xe())})}function ws(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return ws(t)}}function Es(e,t){return j(()=>{let r=ws(e);return typeof r!="undefined"?Ln(r,e,t):y})}var Ss=_r(Mo());var op=0,Ts=R(b(window,"keydown").pipe(f(()=>!0)),R(b(window,"keyup"),b(window,"contextmenu")).pipe(f(()=>!1))).pipe(J(!1),se(1));function Os(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return Os(t)}}function ip(e){return Re(e).pipe(f(({width:t})=>({scrollable:Mr(e).width>t})),fe("scrollable"))}function Ls(e,t){let{matches:r}=matchMedia("(hover)"),n=j(()=>{let o=new I,i=o.pipe(Gn(1));o.subscribe(({scrollable:m})=>{m&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let a=[],s=e.closest("pre"),c=s.closest("[id]"),l=c?c.id:op++;s.id=`__code_${l}`;let u=[],p=e.closest(".highlight");if(p instanceof HTMLElement){let m=Os(p);if(typeof m!="undefined"&&(p.classList.contains("annotate")||X("content.code.annotate"))){let h=Ln(m,e,t);u.push(Re(p).pipe(Q(i),f(({width:v,height:x})=>v&&x),ie(),g(v=>v?h:y)))}}let d=P(":scope > span[id]",e);if(d.length&&(e.classList.add("md-code__content"),e.closest(".select")||X("content.code.select")&&!e.closest(".no-select"))){let m=+d[0].id.split("-").pop(),h=ds();a.push(h),X("content.tooltips")&&u.push(Ge(h,{viewport$}));let v=b(h,"click").pipe(Or(M=>!M,!1),$(()=>h.blur()),xe());v.subscribe(M=>{h.classList.toggle("md-code__button--active",M)});let x=me(d).pipe(oe(M=>Ft(M).pipe(f(O=>[M,O]))));v.pipe(g(M=>M?x:y)).subscribe(([M,O])=>{let N=Le(".hll.select",M);if(N&&!O)N.replaceWith(...Array.from(N.childNodes));else if(!N&&O){let ee=document.createElement("span");ee.className="hll select",ee.append(...Array.from(M.childNodes).slice(1)),M.append(ee)}});let w=me(d).pipe(oe(M=>b(M,"mousedown").pipe($(O=>O.preventDefault()),f(()=>M)))),E=v.pipe(g(M=>M?w:y),pe(Ts),f(([M,O])=>{var ee;let N=d.indexOf(M)+m;if(O===!1)return[N,N];{let le=P(".hll",e).map(ce=>d.indexOf(ce.parentElement)+m);return(ee=window.getSelection())==null||ee.removeAllRanges(),[Math.min(N,...le),Math.max(N,...le)]}})),_=_o(y).pipe(L(M=>M.startsWith(`__codelineno-${l}-`)));_.subscribe(M=>{let[,,O]=M.split("-"),N=O.split(":").map(le=>+le-m+1);N.length===1&&N.push(N[0]);for(let le of P(".hll:not(.select)",e))le.replaceWith(...Array.from(le.childNodes));let ee=d.slice(N[0]-1,N[1]);for(let le of ee){let ce=document.createElement("span");ce.className="hll",ce.append(...Array.from(le.childNodes).slice(1)),le.append(ce)}}),_.pipe(Me(1),Ie(ge)).subscribe(M=>{if(M.includes(":")){let O=document.getElementById(M.split(":")[0]);O&&setTimeout(()=>{let N=O,ee=-64;for(;N!==document.body;)ee+=N.offsetTop,N=N.offsetParent;window.scrollTo({top:ee})},1)}});let be=me(P('a[href^="#__codelineno"]',p)).pipe(oe(M=>b(M,"click").pipe($(O=>O.preventDefault()),f(()=>M)))).pipe(Q(i),pe(Ts),f(([M,O])=>{let ee=+G(`[id="${M.hash.slice(1)}"]`).parentElement.id.split("-").pop();if(O===!1)return[ee,ee];{let le=P(".hll",e).map(ce=>+ce.parentElement.id.split("-").pop());return[Math.min(ee,...le),Math.max(ee,...le)]}}));R(E,be).subscribe(M=>{let O=`#__codelineno-${l}-`;M[0]===M[1]?O+=M[0]:O+=`${M[0]}:${M[1]}`,history.replaceState({},"",O),window.dispatchEvent(new HashChangeEvent("hashchange",{newURL:window.location.origin+window.location.pathname+O,oldURL:window.location.href}))})}if(Ss.default.isSupported()&&(e.closest(".copy")||X("content.code.copy")&&!e.closest(".no-copy"))){let m=ms(s.id);a.push(m),X("content.tooltips")&&u.push(Ge(m,{viewport$}))}if(a.length){let m=hs();m.append(...a),s.insertBefore(m,e)}return ip(e).pipe($(m=>o.next(m)),V(()=>o.complete()),f(m=>H({ref:e},m)),Rt(R(...u).pipe(Q(i))))});return X("content.lazy")?Et(e).pipe(L(o=>o),Me(1),g(()=>n)):n}function ap(e,{target$:t,print$:r}){let n=!0;return R(t.pipe(f(o=>o.closest("details:not([open])")),L(o=>e===o),f(()=>({action:"open",reveal:!0}))),r.pipe(L(o=>o||!n),$(()=>n=e.open),f(o=>({action:o?"open":"close"}))))}function Ms(e,t){return j(()=>{let r=new I;return r.subscribe(({action:n,reveal:o})=>{e.toggleAttribute("open",n==="open"),o&&e.scrollIntoView()}),ap(e,t).pipe($(n=>r.next(n)),V(()=>r.complete()),f(n=>H({ref:e},n)))})}var ks=0,As=new Map;function sp(e){let t=document.createElement("h3");t.innerHTML=e.innerHTML;let r=[t],n=e.nextElementSibling;for(;n&&!(n instanceof HTMLHeadingElement);)r.push(n.cloneNode(!0)),n=n.nextElementSibling;return r}function cp(e,t){for(let r of P("[href], [src]",e))for(let n of["href","src"]){let o=r.getAttribute(n);if(o&&!/^(?:[a-z]+:)?\/\//i.test(o)){r[n]=new URL(r.getAttribute(n),t).toString();break}}for(let r of P("[name^=__], [for]",e))for(let n of["id","for","name"]){let o=r.getAttribute(n);o&&r.setAttribute(n,`${o}$preview_${ks}`)}return ks++,Y(e)}function lp(e){let t=As.get(e.toString());return t?Y(t):En(e).pipe(g(r=>cp(r,e)),f(r=>(As.set(e.toString(),r),r)))}function Cs(e,t){let{sitemap$:r}=t;if(!(e instanceof HTMLAnchorElement))return y;if(!(X("navigation.instant.preview")||e.hasAttribute("data-preview")))return y;e.removeAttribute("title");let n=re([ir(e),Ft(e).pipe(ke(1))]).pipe(f(([i,a])=>i||a),ie(),L(i=>i));return $t([r,n]).pipe(g(([i])=>{let a=new URL(e.href);return a.search=a.hash="",i.has(`${a}`)?Y(a):y}),g(i=>lp(i)),g(i=>{let a=e.hash?`article [id="${decodeURIComponent(e.hash.slice(1))}"]`:"article h1",s=Le(a,i);return typeof s=="undefined"?y:Y(sp(s))})).pipe(g(i=>{let a=new U(s=>{let c=On(...i);return s.next(c),document.body.append(c),()=>c.remove()});return Rr(e,H({content$:a},t))}))}var Hs=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.flowchartTitleText{fill:var(--md-mermaid-label-fg-color)}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel p,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel p{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color)}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}.classDiagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs marker.marker.composition.class path,defs marker.marker.dependency.class path,defs marker.marker.extension.class path{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs marker.marker.aggregation.class path{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}.statediagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel,.nodeLabel p{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}a .nodeLabel{text-decoration:underline}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}[id^=entity] path,[id^=entity] rect{fill:var(--md-default-bg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs .marker.oneOrMore.er *,defs .marker.onlyOne.er *,defs .marker.zeroOrMore.er *,defs .marker.zeroOrOne.er *{stroke:var(--md-mermaid-edge-color)!important}text:not([class]):last-child{fill:var(--md-mermaid-label-fg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var ko,pp=0;function fp(){return typeof mermaid=="undefined"||mermaid instanceof Element?ar("https://unpkg.com/mermaid@11/dist/mermaid.min.js"):Y(void 0)}function $s(e){return e.classList.remove("mermaid"),ko||(ko=fp().pipe($(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Hs,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),f(()=>{}),se(1))),ko.subscribe(()=>Uo(null,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${pp++}`,r=A("div",{class:"mermaid"}),n=e.textContent,{svg:o,fn:i}=yield mermaid.render(t,n),a=r.attachShadow({mode:"closed"});a.innerHTML=o,e.replaceWith(r),i==null||i(a)})),ko.pipe(f(()=>({ref:e})))}var Ps=A("table");function Is(e){return e.replaceWith(Ps),Ps.replaceWith(gs(e)),Y({ref:e})}function mp(e){let t=e.find(r=>r.checked)||e[0];return R(...e.map(r=>b(r,"change").pipe(f(()=>G(`label[for="${r.id}"]`))))).pipe(J(G(`label[for="${t.id}"]`)),f(r=>({active:r})))}function Rs(e,{viewport$:t,target$:r}){let n=G(".tabbed-labels",e),o=P(":scope > input",e),i=Oo("prev");e.append(i);let a=Oo("next");return e.append(a),j(()=>{let s=new I,c=s.pipe(he(),ye(!0));re([s,Re(e),Et(e)]).pipe(Q(c),Xe(1,je)).subscribe({next([{active:l},u]){let p=wt(l),{width:d}=Ae(l);e.style.setProperty("--md-indicator-x",`${p.x}px`),e.style.setProperty("--md-indicator-width",`${d}px`);let m=ln(n);(p.xm.x+u.width)&&n.scrollTo({left:Math.max(0,p.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),re([Ut(n),Re(n)]).pipe(Q(c)).subscribe(([l,u])=>{let p=Mr(n);i.hidden=l.x<16,a.hidden=l.x>p.width-u.width-16}),R(b(i,"click").pipe(f(()=>-1)),b(a,"click").pipe(f(()=>1))).pipe(Q(c)).subscribe(l=>{let{width:u}=Ae(n);n.scrollBy({left:u*l,behavior:"smooth"})}),r.pipe(Q(c),L(l=>o.includes(l))).subscribe(l=>l.click()),n.classList.add("tabbed-labels--linked");for(let l of o){let u=G(`label[for="${l.id}"]`);u.replaceChildren(A("a",{href:`#${u.htmlFor}`,tabIndex:-1},...Array.from(u.childNodes))),b(u.firstElementChild,"click").pipe(Q(c),L(p=>!(p.metaKey||p.ctrlKey)),$(p=>{p.preventDefault(),p.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${u.htmlFor}`),u.click()})}return X("content.tabs.link")&&s.pipe(ke(1),pe(t)).subscribe(([{active:l},{offset:u}])=>{let p=l.innerText.trim();if(l.hasAttribute("data-md-switching"))l.removeAttribute("data-md-switching");else{let d=e.offsetTop-u.y;for(let h of P("[data-tabs]"))for(let v of P(":scope > input",h)){let x=G(`label[for="${v.id}"]`);if(x!==l&&x.innerText.trim()===p){x.setAttribute("data-md-switching",""),v.click();break}}window.scrollTo({top:e.offsetTop-d});let m=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([p,...m])])}}),s.pipe(Q(c)).subscribe(()=>{for(let l of P("audio, video",e))l.offsetWidth&&l.autoplay?l.play().catch(()=>{}):l.pause()}),mp(o).pipe($(l=>s.next(l)),V(()=>s.complete()),f(l=>H({ref:e},l)))}).pipe(Ht(ge))}function js(e,t){let{viewport$:r,target$:n,print$:o}=t;return R(...P(".annotate:not(.highlight)",e).map(i=>Es(i,{target$:n,print$:o})),...P("pre:not(.mermaid) > code",e).map(i=>Ls(i,{target$:n,print$:o})),...P("a",e).map(i=>Cs(i,t)),...P("pre.mermaid",e).map(i=>$s(i)),...P("table:not([class])",e).map(i=>Is(i)),...P("details",e).map(i=>Ms(i,{target$:n,print$:o})),...P("[data-tabs]",e).map(i=>Rs(i,{viewport$:r,target$:n})),...P("[title]:not([data-preview])",e).filter(()=>X("content.tooltips")).map(i=>Ge(i,{viewport$:r})),...P(".footnote-ref",e).filter(()=>X("content.footnote.tooltips")).map(i=>Rr(i,{content$:new U(a=>{let s=new URL(i.href).hash.slice(1),c=Array.from(document.getElementById(s).cloneNode(!0).children),l=On(...c);return a.next(l),document.body.append(l),()=>l.remove()}),viewport$:r})))}function dp(e,{alert$:t}){return t.pipe(g(r=>R(Y(!0),Y(!1).pipe(It(2e3))).pipe(f(n=>({message:r,active:n})))))}function Fs(e,t){let r=G(".md-typeset",e);return j(()=>{let n=new I;return n.subscribe(({message:o,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=o}),dp(e,t).pipe($(o=>n.next(o)),V(()=>n.complete()),f(o=>H({ref:e},o)))})}function hp({viewport$:e}){if(!X("header.autohide"))return Y(!1);let t=e.pipe(f(({offset:{y:o}})=>o),Pt(2,1),f(([o,i])=>[oMath.abs(i-o.y)>100),f(([,[o]])=>o),ie()),n=Tn("search");return re([e,n]).pipe(f(([{offset:o},i])=>o.y>400&&!i),ie(),g(o=>o?r:Y(!1)),J(!1))}function Us(e,t){return j(()=>re([Re(e),hp(t)])).pipe(f(([{height:r},n])=>({height:r,hidden:n})),ie((r,n)=>r.height===n.height&&r.hidden===n.hidden),se(1))}function Ns(e,{viewport$:t,header$:r,main$:n}){return j(()=>{let o=new I,i=o.pipe(he(),ye(!0));o.pipe(fe("active"),Ze(r)).subscribe(([{active:s},{hidden:c}])=>{e.classList.toggle("md-header--shadow",s&&!c),e.hidden=c});let a=me(P("[title]",e)).pipe(L(()=>X("content.tooltips")),oe(s=>Ge(s,{viewport$:t})));return n.subscribe(o),r.pipe(Q(i),f(s=>H({ref:e},s)),Rt(a.pipe(Q(i))))})}function vp(e,{viewport$:t,header$:r}){return Sn(e,{viewport$:t,header$:r}).pipe(f(({offset:{y:n}})=>{let{height:o}=Ae(e);return{active:o>0&&n>=o}}),fe("active"))}function Ds(e,t){return j(()=>{let r=new I;r.subscribe({next({active:o}){e.classList.toggle("md-header__title--active",o)},complete(){e.classList.remove("md-header__title--active")}});let n=Le(".md-content h1");return typeof n=="undefined"?y:vp(n,t).pipe($(o=>r.next(o)),V(()=>r.complete()),f(o=>H({ref:e},o)))})}function Ws(e,{viewport$:t,header$:r}){let n=r.pipe(f(({height:i})=>i),ie()),o=n.pipe(g(()=>Re(e).pipe(f(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),fe("bottom"))));return re([n,o,t]).pipe(f(([i,{top:a,bottom:s},{offset:{y:c},size:{height:l}}])=>(l=Math.max(0,l-Math.max(0,a-c,i)-Math.max(0,l+c-s)),{offset:a-i,height:l,active:a-i<=c})),ie((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function bp(e){let t=__md_get("__palette")||{index:e.findIndex(n=>matchMedia(n.getAttribute("data-md-color-media")).matches)},r=Math.max(0,Math.min(t.index,e.length-1));return Y(...e).pipe(oe(n=>b(n,"change").pipe(f(()=>n))),J(e[r]),f(n=>({index:e.indexOf(n),color:{media:n.getAttribute("data-md-color-media"),scheme:n.getAttribute("data-md-color-scheme"),primary:n.getAttribute("data-md-color-primary"),accent:n.getAttribute("data-md-color-accent")}})),se(1))}function Vs(e){let t=P("input",e),r=A("meta",{name:"theme-color"});document.head.appendChild(r);let n=A("meta",{name:"color-scheme"});document.head.appendChild(n);let o=Ir("(prefers-color-scheme: light)");return j(()=>{let i=new I;return i.subscribe(a=>{if(document.body.setAttribute("data-md-color-switching",""),a.color.media==="(prefers-color-scheme)"){let s=matchMedia("(prefers-color-scheme: light)"),c=document.querySelector(s.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");a.color.scheme=c.getAttribute("data-md-color-scheme"),a.color.primary=c.getAttribute("data-md-color-primary"),a.color.accent=c.getAttribute("data-md-color-accent")}for(let[s,c]of Object.entries(a.color))document.body.setAttribute(`data-md-color-${s}`,c);for(let s=0;sa.key==="Enter"),pe(i,(a,s)=>s)).subscribe(({index:a})=>{a=(a+1)%t.length,t[a].click(),t[a].focus()}),i.pipe(f(()=>{let a=ht("header"),s=window.getComputedStyle(a);return n.content=s.colorScheme,s.backgroundColor.match(/\d+/g).map(c=>(+c).toString(16).padStart(2,"0")).join("")})).subscribe(a=>r.content=`#${a}`),i.pipe(Ie(ge)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),bp(t).pipe(Q(o.pipe(ke(1))),jt(),$(a=>i.next(a)),V(()=>i.complete()),f(a=>H({ref:e},a)))})}function zs(e,{progress$:t}){return j(()=>{let r=new I;return r.subscribe(({value:n})=>{e.style.setProperty("--md-progress-value",`${n}`)}),t.pipe($(n=>r.next({value:n})),V(()=>r.complete()),f(n=>({ref:e,value:n})))})}var qs='.v u{text-decoration:underline!important;text-decoration-style:wavy!important;text-decoration-thickness:1px!important}.p{-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);background-color:rgba(var(--color-backdrop)/var(--alpha-lighter));cursor:pointer;height:100%;pointer-events:auto;position:absolute;transition:opacity .25s;width:100%}.p.m{opacity:0;pointer-events:none;transition:opacity .35s}.r{align-items:center;background-color:initial;border:none;border-radius:var(--space-2);cursor:pointer;display:flex;flex-shrink:0;font-family:var(--font-family);height:36px;justify-content:center;outline:none;padding:0;position:relative;transition:background-color .25s,color .25s;width:36px;z-index:1}.r svg{stroke:rgb(var(--color-foreground));height:18px;opacity:.5;width:18px}.r:before{background-color:rgb(var(--color-background-subtle));border-radius:var(--border-radius-2);content:"";inset:0;opacity:0;position:absolute;transform:scale(.75);transition:transform 125ms,opacity 125ms;z-index:0}.r:hover:before{opacity:1;transform:scale(1)}.r.c{cursor:auto}.r.c:before{display:none}.n{-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);background-color:rgba(var(--color-background)/var(--alpha-light));border-radius:var(--space-3);box-shadow:0 0 60px #0000000d;display:flex;height:480px;overflow:hidden;pointer-events:auto;position:absolute;transition:transform .25s cubic-bezier(.16,1,.3,1),opacity .25s;width:640px}.n.l{opacity:0;pointer-events:none;transform:scale(1.1);transition:transform .25s .15s,opacity .15s}@media (max-width:680px){.n{border-radius:0;height:100%;width:100%}}.u{display:flex;flex-basis:min-content;flex-direction:column;flex-grow:1;flex-shrink:0}@keyframes d{0%{transform:scale(0)}50%{transform:scale(1.2)}to{transform:scale(1)}}.y{animation:d .25s ease-in-out;background:var(--color-highlight);border-radius:100%;color:#fff;font-size:8px;font-weight:700;height:12px;padding-top:1px;position:absolute;right:4px;top:4px;width:12px}.i{background-color:rgb(var(--color-background-subtle)/var(--alpha-lighter));flex-shrink:0;overflow:scroll;position:relative;transition:width .35s cubic-bezier(.16,1,.3,1),opacity .25s;width:200px}.i>*{transform:translate(0);transition:transform .25s cubic-bezier(.16,1,.3,1)}.i.l{opacity:0;width:0}.i.l>*{transform:translate(-48px)}@media (max-width:680px){.i{-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);background-color:rgba(var(--color-background-subtle)/var(--alpha-light));box-shadow:0 0 60px #00000026;height:100%;position:absolute;right:0;top:0}}.w{border-bottom:1px solid rgb(var(--color-foreground)/var(--alpha-lightest));display:flex;gap:var(--space-1);padding:var(--space-2)}.k{-webkit-overflow-scrolling:touch;overflow:auto;overscroll-behavior:contain}.z{padding:8px 10px}.X{color:rgb(var(--color-foreground)/var(--alpha-light));padding:var(--space-2);position:absolute;width:200px}.X,.j{display:flex;flex-direction:column}.j{gap:2px;list-style:none;padding:0}.F,.j{margin:0}.F{font-size:16px;font-weight:400}.F,.I{padding:8px}.I{font-size:14px;margin:4px 0 0;opacity:.5}.I,.o{font-size:12px}.o{cursor:pointer;display:flex;padding:4px 8px;position:relative}.o:before{background-color:var(--color-highlight-transparent);border-radius:var(--space-1);content:"";inset:0;opacity:0;position:absolute;transform:scale(.75);transition:transform 125ms,opacity 125ms;z-index:0}.o.g:before,.o:hover:before{opacity:1;transform:scale(1)}.o.g,.o:hover{color:var(--color-highlight)}.R{flex-grow:1}.R,.q{position:relative}.q{font-weight:700}.f{flex-grow:1}.f input{background:#0000;border:none;color:rgb(var(--color-foreground));font-family:var(--font-family);font-size:16px;height:100%;letter-spacing:-.25px;outline:none;width:100%}.b{color:rgb(var(--color-foreground)/var(--alpha-light));display:flex;flex-direction:column;gap:2px;line-height:1.3;list-style:none;margin:var(--space-2);margin-top:0;padding:0}.A,.b li{margin:0}.A{color:rgb(var(--color-foreground)/var(--alpha-lighter));font-size:12px;margin-top:var(--space-2);padding:0 18px}.a{border-radius:var(--space-2);color:inherit;cursor:pointer;display:flex;flex-direction:row;flex-grow:1;padding:8px 10px;position:relative;text-decoration:none}.a:before{background-color:rgb(var(--color-background-subtle));border-radius:var(--border-radius-2);content:"";display:block;inset:0;opacity:0;position:absolute;transform:scale(.9);transition:transform 125ms,opacity 125ms;z-index:0}@media (pointer:fine){.a.h:before,.a:hover:before{opacity:1;transform:scale(1)}}.a mark{background:#0000;color:var(--color-highlight)}.a u{background-color:var(--color-highlight-transparent);border-radius:2px;box-shadow:0 0 0 1px var(--color-highlight-transparent);text-decoration:none}.B{flex-grow:1}.s{margin-right:-8px;opacity:0;position:relative;transform:translate(-2px);transition:transform 125ms,opacity 125ms;z-index:0}@media (pointer:fine){.h>.s,:hover>.s{opacity:1;transform:none}}.x{font-size:14px;margin:0;position:relative}.x code{background:rgb(var(--color-background-subtle));border-radius:var(--space-1);font-size:13px;padding:2px 4px}.t{color:rgb(var(--color-foreground)/var(--alpha-lighter));display:inline-flex;flex-wrap:wrap;font-size:12px;gap:var(--space-1);list-style:none;margin:0;padding:0;position:relative}.t li{white-space:nowrap}.t li:after{content:"/";display:inline;margin-left:var(--space-1)}.t li:last-child:after{content:"";display:none}.e{--space-1:4px;--space-2:calc(var(--space-1)*2);--space-3:calc(var(--space-2)*2);--space-4:calc(var(--space-3)*2);--space-5:calc(var(--space-4)*2);--alpha-light:.7;--alpha-lighter:.54;--alpha-lightest:.1;--color-highlight:var(--md-accent-fg-color,#526cfe);--color-highlight-transparent:var(--md-accent-fg-color--transparent,#526cfe1a);--border-radius-1:var(--space-1);--border-radius-2:var(--space-2);--border-radius-3:calc(var(--space-1) + var(--space-2));--font-family:var(--md-text-font-family,Inter,Roboto Flex,system-ui,sans-serif);--font-size:16px;--line-height:1.5;--letter-spacing:-.5px;-webkit-font-smoothing:antialiased;align-items:center;display:flex;font-family:var(--font-family);font-size:var(--font-size);height:100vh;justify-content:center;letter-spacing:var(--letter-spacing);line-height:var(--line-height);pointer-events:none;position:absolute;width:100vw}@media (pointer:coarse){.e{height:-webkit-fill-available}}.e *,.e :after,.e :before{box-sizing:border-box}';function Ks(e,{index$:t}){let r=Ue(),n=document.createElement("div");document.body.appendChild(n),n.style.position="fixed",n.style.height="100%",n.style.top="0",n.style.zIndex="4";let o=n.attachShadow({mode:"open"});o.appendChild(A("style",{},qs.toString()));try{Ya(r.search,{highlight:r.features.includes("search.highlight")}),me(t).subscribe(i=>{for(let a of i.items)a.location=new URL(a.location,r.base).toString();Ja(i,o)}),b(e,"click").subscribe(()=>{go()}),Tn("search").pipe(ke(1)).subscribe(()=>go())}catch(i){e.hidden=!0;let a=G("label[for=__search]");a.hidden=!0}return Ke}var Bs=_r(So());function Ys(e,{index$:t,location$:r}){return re([t,r.pipe(J(Ye()),L(n=>!!n.searchParams.get("h")))]).pipe(f(([n,o])=>_p(n.config)(o.searchParams.get("h"))),f(n=>{var a;let o=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let s=i.nextNode();s;s=i.nextNode())if((a=s.parentElement)!=null&&a.offsetHeight){let c=s.textContent,l=n(c);l.length>c.length&&o.set(s,l)}for(let[s,c]of o){let{childNodes:l}=A("span",null,c);s.replaceWith(...Array.from(l))}return{ref:e,nodes:o}}))}function _p(e){let t=e.separator.split("|").map(o=>o.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":o).join("|"),r=new RegExp(t,"img"),n=(o,i,a)=>`${i}${a}`;return o=>{o=o.replace(/\s+/g," ").replace(/&/g,"&").trim();let i=new RegExp(`(^|${e.separator}|)(${o.split(r).map(a=>a.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&")).filter(a=>a.length>0).join("|")})`,"img");return a=>(0,Bs.default)(a).replace(i,n).replace(/<\/mark>(\s+)]*>/img,"$1")}}function yp(e,{viewport$:t,main$:r}){let n=e.closest(".md-grid"),o=n.offsetTop-n.parentElement.offsetTop;return re([r,t]).pipe(f(([{offset:i,height:a},{offset:{y:s}}])=>(a=a+Math.min(o,Math.max(0,s-i))-o,{height:a,locked:s>=i+o})),ie((i,a)=>i.height===a.height&&i.locked===a.locked))}function Ao(e,n){var o=n,{header$:t}=o,r=gr(o,["header$"]);let i=G(".md-sidebar__scrollwrap",e),{y:a}=wt(i);return j(()=>{let s=new I,c=s.pipe(he(),ye(!0)),l=s.pipe(Xe(0,je));return l.pipe(pe(t)).subscribe({next([{height:u},{height:p}]){i.style.height=`${u-2*a}px`,e.style.top=`${p}px`},complete(){i.style.height="",e.style.top=""}}),l.pipe(Sr()).subscribe(()=>{for(let u of P(".md-nav__link--active[href]",e)){if(!u.clientHeight)continue;let p=u.closest(".md-sidebar__scrollwrap");if(typeof p!="undefined"){let d=u.offsetTop-p.offsetTop,{height:m}=Ae(p);p.scrollTo({top:d-m/2})}}}),me(P("label[tabindex]",e)).pipe(oe(u=>b(u,"click").pipe(Ie(ge),f(()=>u),Q(c)))).subscribe(u=>{let p=G(`[id="${u.htmlFor}"]`);G(`[aria-labelledby="${u.id}"]`).setAttribute("aria-expanded",`${p.checked}`)}),X("content.tooltips")&&me(P("abbr[title]",e)).pipe(oe(u=>Ge(u,{viewport$})),Q(c)).subscribe(),yp(e,r).pipe($(u=>s.next(u)),V(()=>s.complete()),f(u=>H({ref:e},u)))})}function Gs(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return $t(et(`${r}/releases/latest`).pipe(_e(()=>y),f(n=>({version:n.tag_name})),ot({})),et(r).pipe(_e(()=>y),f(n=>({stars:n.stargazers_count,forks:n.forks_count})),ot({}))).pipe(f(([n,o])=>H(H({},n),o)))}else{let r=`https://api.github.com/users/${e}`;return et(r).pipe(f(n=>({repositories:n.public_repos})),ot({}))}}function Js(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return $t(et(`${r}/releases/permalink/latest`).pipe(_e(()=>y),f(({tag_name:n})=>({version:n})),ot({})),et(r).pipe(_e(()=>y),f(({star_count:n,forks_count:o})=>({stars:n,forks:o})),ot({}))).pipe(f(([n,o])=>H(H({},n),o)))}function Xs(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,n]=t;return Gs(r,n)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,n]=t;return Js(r,n)}return y}var xp;function wp(e){return xp||(xp=j(()=>{let t=__md_get("__source",sessionStorage);if(t)return Y(t);if(Ee("consent").length){let n=__md_get("__consent");if(!(n&&n.github))return y}return Xs(e.href).pipe($(n=>__md_set("__source",n,sessionStorage)))}).pipe(_e(()=>y),L(t=>Object.keys(t).length>0),f(t=>({facts:t})),se(1)))}function Zs(e){let t=G(":scope > :last-child",e);return j(()=>{let r=new I;return r.subscribe(({facts:n})=>{t.appendChild(bs(n)),t.classList.add("md-source__repository--active")}),wp(e).pipe($(n=>r.next(n)),V(()=>r.complete()),f(n=>H({ref:e},n)))})}function Ep(e,{viewport$:t,header$:r}){return Re(document.body).pipe(g(()=>Sn(e,{header$:r,viewport$:t})),f(({offset:{y:n}})=>({hidden:n>=10})),fe("hidden"))}function Qs(e,t){return j(()=>{let r=new I;return r.subscribe({next({hidden:n}){e.hidden=n},complete(){e.hidden=!1}}),(X("navigation.tabs.sticky")?Y({hidden:!1}):Ep(e,t)).pipe($(n=>r.next(n)),V(()=>r.complete()),f(n=>H({ref:e},n)))})}function Tp(e,{viewport$:t,header$:r}){let n=new Map,o=P(".md-nav__link",e);for(let s of o){let c=decodeURIComponent(s.hash.substring(1)),l=Le(`[id="${c}"]`);typeof l!="undefined"&&n.set(s,l)}let i=r.pipe(fe("height"),f(({height:s})=>{let c=ht("main"),l=G(":scope > :first-child",c);return s+.9*(l.offsetTop-c.offsetTop)}),xe());return Re(document.body).pipe(fe("height"),g(s=>j(()=>{let c=[];return Y([...n].reduce((l,[u,p])=>{for(;c.length&&n.get(c[c.length-1]).tagName>=p.tagName;)c.pop();let d=p.offsetTop;for(;!d&&p.parentElement;)p=p.parentElement,d=p.offsetTop;let m=p.offsetParent;for(;m;m=m.offsetParent)d+=m.offsetTop;return l.set([...c=[...c,u]].reverse(),d)},new Map))}).pipe(f(c=>new Map([...c].sort(([,l],[,u])=>l-u))),Ze(i),g(([c,l])=>t.pipe(Or(([u,p],{offset:{y:d},size:m})=>{let h=d+m.height>=Math.floor(s.height);for(;p.length;){let[,v]=p[0];if(v-l=d&&!h)p=[u.pop(),...p];else break}return[u,p]},[[],[...c]]),ie((u,p)=>u[0]===p[0]&&u[1]===p[1])))))).pipe(f(([s,c])=>({prev:s.map(([l])=>l),next:c.map(([l])=>l)})),J({prev:[],next:[]}),Pt(2,1),f(([s,c])=>s.prev.length{let i=new I,a=i.pipe(he(),ye(!0));if(i.subscribe(({prev:s,next:c})=>{for(let[l]of c)l.classList.remove("md-nav__link--passed"),l.classList.remove("md-nav__link--active");for(let[l,[u]]of s.entries())u.classList.add("md-nav__link--passed"),u.classList.toggle("md-nav__link--active",l===s.length-1)}),X("toc.follow")){let s=R(t.pipe(Be(1),f(()=>{})),t.pipe(Be(250),f(()=>"smooth")));i.pipe(L(({prev:c})=>c.length>0),Ze(n.pipe(Ie(ge))),pe(s)).subscribe(([[{prev:c}],l])=>{let[u]=c[c.length-1];if(u.offsetHeight){let p=ki(u);if(typeof p!="undefined"){let d=u.offsetTop-p.offsetTop,{height:m}=Ae(p);p.scrollTo({top:d-m/2,behavior:l})}}})}return X("navigation.tracking")&&t.pipe(Q(a),fe("offset"),Be(250),ke(1),Q(o.pipe(ke(1))),jt({delay:250}),pe(i)).subscribe(([,{prev:s}])=>{let c=Ye(),l=s[s.length-1];if(l&&l.length){let[u]=l,{hash:p}=new URL(u.href);c.hash!==p&&(c.hash=p,history.replaceState({},"",`${c}`))}else c.hash="",history.replaceState({},"",`${c}`)}),Tp(e,{viewport$:t,header$:r}).pipe($(s=>i.next(s)),V(()=>i.complete()),f(s=>H({ref:e},s)))})}function Sp(e,{viewport$:t,main$:r,target$:n}){let o=t.pipe(f(({offset:{y:a}})=>a),Pt(2,1),f(([a,s])=>a>s&&s>0),ie()),i=r.pipe(f(({active:a})=>a));return re([i,o]).pipe(f(([a,s])=>!(a&&s)),ie(),Q(n.pipe(ke(1))),ye(!0),jt({delay:250}),f(a=>({hidden:a})))}function tc(e,{viewport$:t,header$:r,main$:n,target$:o}){let i=new I,a=i.pipe(he(),ye(!0));return i.subscribe({next({hidden:s}){e.hidden=s,s?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(Q(a),fe("height")).subscribe(({height:s})=>{e.style.top=`${s+16}px`}),b(e,"click").subscribe(s=>{s.preventDefault(),window.scrollTo({top:0})}),Sp(e,{viewport$:t,main$:n,target$:o}).pipe($(s=>i.next(s)),V(()=>i.complete()),f(s=>H({ref:e},s)))}function rc(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,t.port&&(e.port=t.port),e}function Op(e,t){let r=new Map;for(let n of P("url",e)){let o=G("loc",n),i=[rc(new URL(o.textContent),t)];r.set(`${i[0]}`,i);for(let a of P("[rel=alternate]",n)){let s=a.getAttribute("href");s!=null&&i.push(rc(new URL(s),t))}}return r}function dr(e){return ns(new URL("sitemap.xml",e)).pipe(f(t=>Op(t,new URL(e))),_e(()=>Y(new Map)),xe())}function nc({document$:e}){let t=new Map;e.pipe(g(()=>P("link[rel=alternate]")),f(r=>new URL(r.href)),L(r=>!t.has(r.toString())),oe(r=>dr(r).pipe(f(n=>[r,n]),_e(()=>y)))).subscribe(([r,n])=>{t.set(r.toString().replace(/\/$/,""),n)}),b(document.body,"click").pipe(L(r=>!r.metaKey&&!r.ctrlKey),g(r=>{if(r.target instanceof Element){let n=r.target.closest("a");if(n&&!n.target){let o=[...t].find(([p])=>n.href.startsWith(`${p}/`));if(typeof o=="undefined")return y;let[i,a]=o,s=Ye();if(s.href.startsWith(i))return y;let c=Ue(),l=s.href.replace(c.base,"");l=`${i}/${l}`;let u=a.has(l.split("#")[0])?new URL(l,c.base):new URL(i);return r.preventDefault(),Y(u)}}return y})).subscribe(r=>dt(r,!0))}var Co=_r(Mo());function Lp(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function oc({alert$:e}){Co.default.isSupported()&&new U(t=>{new Co.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||Lp(G(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe($(t=>{t.trigger.focus()}),f(()=>Bt("clipboard.copied"))).subscribe(e)}function ic(e,t){if(!(e.target instanceof Element))return y;let r=e.target.closest("a");if(r===null)return y;if(r.target||e.metaKey||e.ctrlKey)return y;let n=new URL(r.href);return n.search=n.hash="",t.has(`${n}`)?(e.preventDefault(),Y(r)):y}function ac(e){let t=new Map;for(let r of P(":scope > *",e.head))t.set(r.outerHTML,r);return t}function sc(e){for(let t of P("[href], [src]",e))for(let r of["href","src"]){let n=t.getAttribute(r);if(n&&!/^(?:[a-z]+:)?\/\//i.test(n)){t[r]=t[r];break}}return Y(e)}function Mp(e){for(let n of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...X("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let o=Le(n),i=Le(n,e);typeof o!="undefined"&&typeof i!="undefined"&&o.replaceWith(i)}let t=ac(document);for(let[n,o]of ac(e))t.has(n)?t.delete(n):document.head.appendChild(o);for(let n of t.values()){let o=n.getAttribute("name");o!=="theme-color"&&o!=="color-scheme"&&n.remove()}let r=ht("container");return nt(P("script",r)).pipe(g(n=>{let o=e.createElement("script");if(n.src){for(let i of n.getAttributeNames())o.setAttribute(i,n.getAttribute(i));return n.replaceWith(o),new U(i=>{o.onload=()=>i.complete()})}else return o.textContent=n.textContent,n.replaceWith(o),y}),he(),ye(document))}function cc({sitemap$:e,location$:t,viewport$:r,progress$:n}){if(location.protocol==="file:")return Ke;Y(document).subscribe(sc);let o=b(document.body,"click").pipe(Ze(e),g(([s,c])=>ic(s,c)),f(({href:s})=>new URL(s)),xe()),i=b(window,"popstate").pipe(f(Ye),xe());o.pipe(pe(r)).subscribe(([s,{offset:c}])=>{history.replaceState(c,""),history.pushState(null,"",s)}),R(o,i).subscribe(t);let a=t.pipe(fe("pathname"),g(s=>En(s,{progress$:n}).pipe(_e(()=>(dt(s,!0),y)))),g(sc),g(Mp),xe());return R(a.pipe(pe(t,(s,c)=>c)),a.pipe(g(()=>t),fe("hash")),t.pipe(ie((s,c)=>s.pathname===c.pathname&&s.hash===c.hash),g(()=>o),$(()=>history.back()))).subscribe(s=>{var c,l;history.state!==null||!s.hash?window.scrollTo(0,(l=(c=history.state)==null?void 0:c.y)!=null?l:0):(history.scrollRestoration="auto",es(s.hash),history.scrollRestoration="manual")}),t.subscribe(()=>{history.scrollRestoration="manual"}),b(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),r.pipe(fe("offset"),Be(100)).subscribe(({offset:s})=>{history.replaceState(s,"")}),X("navigation.instant.prefetch")&&R(b(document.body,"mousemove"),b(document.body,"focusin")).pipe(Ze(e),g(([s,c])=>ic(s,c)),Be(25),Yn(({href:s})=>s),cn(s=>{let c=document.createElement("link");return c.rel="prefetch",c.href=s.toString(),document.head.appendChild(c),b(c,"load").pipe(f(()=>c),Me(1))})).subscribe(s=>s.remove()),a}function lc(e){var u;let{selectedVersionSitemap:t,selectedVersionBaseURL:r,currentLocation:n,currentBaseURL:o}=e,i=(u=Ho(o))==null?void 0:u.pathname;if(i===void 0)return;let a=kp(n.pathname,i);if(a===void 0)return;let s=Cp(t.keys());if(!t.has(s))return;let c=Ho(a,s);if(!c||!t.has(c.href))return;let l=Ho(a,r);if(l)return l.hash=n.hash,l.search=n.search,l}function Ho(e,t){try{return new URL(e,t)}catch(r){return}}function kp(e,t){if(e.startsWith(t))return e.slice(t.length)}function Ap(e,t){let r=Math.min(e.length,t.length),n;for(n=0;ny)),n=r.pipe(f(o=>{let[,i]=t.base.match(/([^/]+)\/?$/);return o.find(({version:a,aliases:s})=>a===i||s.includes(i))||o[0]}));r.pipe(f(o=>new Map(o.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),g(o=>b(document.body,"click").pipe(L(i=>!i.metaKey&&!i.ctrlKey),pe(n),g(([i,a])=>{if(i.target instanceof Element){let s=i.target.closest("a");if(s&&!s.target&&o.has(s.href)){let c=s.href;return!i.target.closest(".md-version")&&o.get(c)===a?y:(i.preventDefault(),Y(new URL(c)))}}return y}),g(i=>dr(i).pipe(f(a=>{var s;return(s=lc({selectedVersionSitemap:a,selectedVersionBaseURL:i,currentLocation:Ye(),currentBaseURL:t.base}))!=null?s:i})))))).subscribe(o=>dt(o,!0)),re([r,n]).subscribe(([o,i])=>{G(".md-header__topic").appendChild(_s(o,i))}),e.pipe(g(()=>n)).subscribe(o=>{var s;let i=new URL(t.base),a=__md_get("__outdated",sessionStorage,i);if(a===null){a=!0;let c=((s=t.version)==null?void 0:s.default)||"latest";Array.isArray(c)||(c=[c]);e:for(let l of c)for(let u of o.aliases.concat(o.version))if(new RegExp(l,"i").test(u)){a=!1;break e}__md_set("__outdated",a,sessionStorage,i)}if(a)for(let c of Ee("outdated"))c.hidden=!1})}function pc({document$:e,viewport$:t}){e.pipe(g(()=>P(".md-ellipsis")),oe(r=>Et(r).pipe(Q(e.pipe(ke(1))),L(n=>n),f(()=>r),Me(1))),L(r=>r.offsetWidth{let n=r.innerText,o=r.closest("a")||r;return o.title=n,X("content.tooltips")?Ge(o,{viewport$:t}).pipe(Q(e.pipe(ke(1))),V(()=>o.removeAttribute("title"))):y})).subscribe(),X("content.tooltips")&&e.pipe(g(()=>P(".md-status")),oe(r=>Ge(r,{viewport$:t}))).subscribe()}function fc({document$:e,tablet$:t}){e.pipe(g(()=>P(".md-toggle--indeterminate")),$(r=>{r.indeterminate=!0,r.checked=!1}),oe(r=>b(r,"change").pipe(Xn(()=>r.classList.contains("md-toggle--indeterminate")),f(()=>r))),pe(t)).subscribe(([r,n])=>{r.classList.remove("md-toggle--indeterminate"),n&&(r.checked=!1)})}function Hp(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function mc({document$:e}){e.pipe(g(()=>P("[data-md-scrollfix]")),$(t=>t.removeAttribute("data-md-scrollfix")),L(Hp),oe(t=>b(t,"touchstart").pipe(f(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let n=e[r];typeof n=="string"?n=document.createTextNode(n):n.parentNode&&n.parentNode.removeChild(n),r?t.insertBefore(this.previousSibling,n):t.replaceChild(n,this)}}}));function $p(){return location.protocol==="file:"?ar(`${new URL("search.js",Mn.base)}`).pipe(f(()=>__index),_e(()=>Ke),se(1)):et(new URL("search.json",Mn.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var vt=Si(),Ur=Za(),hr=ts(Ur),hc=Xa(),ze=cs(),$o=Ir("(min-width: 60em)"),vc=Ir("(min-width: 76.25em)"),bc=rs(),Mn=Ue(),gc=Le(".md-search")?$p():Ke,Po=new I;oc({alert$:Po});nc({document$:vt});var Io=new I,_c=dr(Mn.base);X("navigation.instant")&&cc({sitemap$:_c,location$:Ur,viewport$:ze,progress$:Io}).subscribe(vt);var dc;((dc=Mn.version)==null?void 0:dc.provider)==="mike"&&uc({document$:vt});R(Ur,hr).pipe(It(125)).subscribe(()=>{Eo("drawer",!1),Eo("search",!1)});hc.pipe(L(({mode:e,meta:t})=>e==="global"&&!t)).subscribe(e=>{switch(e.type){case",":case"p":let t=document.querySelector("link[rel=prev]");t instanceof HTMLLinkElement&&dt(t);break;case".":case"n":let r=document.querySelector("link[rel=next]");r instanceof HTMLLinkElement&&dt(r);break;case"/":let n=document.querySelector("[data-md-component=search] button");n instanceof HTMLButtonElement&&n.click();break;case"Enter":let o=xt();o instanceof HTMLLabelElement&&o.click()}});pc({viewport$:ze,document$:vt});fc({document$:vt,tablet$:$o});mc({document$:vt});var Lt=Us(ht("header"),{viewport$:ze}),Fr=vt.pipe(f(()=>ht("main")),g(e=>Ws(e,{viewport$:ze,header$:Lt})),se(1)),Pp=R(...Ee("consent").map(e=>us(e,{target$:hr})),...Ee("dialog").map(e=>Fs(e,{alert$:Po})),...Ee("palette").map(e=>Vs(e)),...Ee("progress").map(e=>zs(e,{progress$:Io})),...Ee("search").map(e=>Ks(e,{index$:gc})),...Ee("source").map(e=>Zs(e))),Ip=j(()=>R(...Ee("announce").map(e=>ls(e)),...Ee("content").map(e=>js(e,{sitemap$:_c,viewport$:ze,target$:hr,print$:bc})),...Ee("content").map(e=>X("search.highlight")?Ys(e,{index$:gc,location$:Ur}):y),...Ee("header").map(e=>Ns(e,{viewport$:ze,header$:Lt,main$:Fr})),...Ee("header-title").map(e=>Ds(e,{viewport$:ze,header$:Lt})),...Ee("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?yo(vc,()=>Ao(e,{viewport$:ze,header$:Lt,main$:Fr})):yo($o,()=>Ao(e,{viewport$:ze,header$:Lt,main$:Fr}))),...Ee("tabs").map(e=>Qs(e,{viewport$:ze,header$:Lt})),...Ee("toc").map(e=>ec(e,{viewport$:ze,header$:Lt,main$:Fr,target$:hr})),...Ee("top").map(e=>tc(e,{viewport$:ze,header$:Lt,main$:Fr,target$:hr})))),yc=vt.pipe(g(()=>Ip),Rt(Pp),se(1));yc.subscribe();window.document$=vt;window.location$=Ur;window.target$=hr;window.keyboard$=hc;window.viewport$=ze;window.tablet$=$o;window.screen$=vc;window.print$=bc;window.alert$=Po;window.progress$=Io;window.component$=yc;})(); -/*! update cache: 20260329054745 */ +/*! update cache: 20260330081601 */ diff --git a/en/chapter_appendix/contribution/index.html b/en/chapter_appendix/contribution/index.html index 2e851c505..164c67a49 100644 --- a/en/chapter_appendix/contribution/index.html +++ b/en/chapter_appendix/contribution/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_appendix/index.html b/en/chapter_appendix/index.html index e2c66ec08..2cee0848b 100644 --- a/en/chapter_appendix/index.html +++ b/en/chapter_appendix/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_appendix/installation/index.html b/en/chapter_appendix/installation/index.html index 1ed8b55bc..eb961f96b 100644 --- a/en/chapter_appendix/installation/index.html +++ b/en/chapter_appendix/installation/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_appendix/terminology/index.html b/en/chapter_appendix/terminology/index.html index 61bbcf582..ad5d8d5df 100644 --- a/en/chapter_appendix/terminology/index.html +++ b/en/chapter_appendix/terminology/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_array_and_linkedlist/array/index.html b/en/chapter_array_and_linkedlist/array/index.html index 28333e130..7351430b5 100644 --- a/en/chapter_array_and_linkedlist/array/index.html +++ b/en/chapter_array_and_linkedlist/array/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_array_and_linkedlist/index.html b/en/chapter_array_and_linkedlist/index.html index 4d6834386..61637fee0 100644 --- a/en/chapter_array_and_linkedlist/index.html +++ b/en/chapter_array_and_linkedlist/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_array_and_linkedlist/linked_list/index.html b/en/chapter_array_and_linkedlist/linked_list/index.html index 817d5f8f0..45be67206 100644 --- a/en/chapter_array_and_linkedlist/linked_list/index.html +++ b/en/chapter_array_and_linkedlist/linked_list/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_array_and_linkedlist/list/index.html b/en/chapter_array_and_linkedlist/list/index.html index 1eccb32bb..923947759 100644 --- a/en/chapter_array_and_linkedlist/list/index.html +++ b/en/chapter_array_and_linkedlist/list/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_array_and_linkedlist/ram_and_cache/index.html b/en/chapter_array_and_linkedlist/ram_and_cache/index.html index 0d3d928f0..12275c691 100644 --- a/en/chapter_array_and_linkedlist/ram_and_cache/index.html +++ b/en/chapter_array_and_linkedlist/ram_and_cache/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_array_and_linkedlist/summary/index.html b/en/chapter_array_and_linkedlist/summary/index.html index d89926dd9..a29d38b19 100644 --- a/en/chapter_array_and_linkedlist/summary/index.html +++ b/en/chapter_array_and_linkedlist/summary/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_backtracking/backtracking_algorithm/index.html b/en/chapter_backtracking/backtracking_algorithm/index.html index 32b7a6c24..98bf8b001 100644 --- a/en/chapter_backtracking/backtracking_algorithm/index.html +++ b/en/chapter_backtracking/backtracking_algorithm/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_backtracking/index.html b/en/chapter_backtracking/index.html index f5d28cfb0..9b3a9a16f 100644 --- a/en/chapter_backtracking/index.html +++ b/en/chapter_backtracking/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_backtracking/n_queens_problem/index.html b/en/chapter_backtracking/n_queens_problem/index.html index 8af2b4e66..705892507 100644 --- a/en/chapter_backtracking/n_queens_problem/index.html +++ b/en/chapter_backtracking/n_queens_problem/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_backtracking/permutations_problem/index.html b/en/chapter_backtracking/permutations_problem/index.html index 7e56116db..084b56190 100644 --- a/en/chapter_backtracking/permutations_problem/index.html +++ b/en/chapter_backtracking/permutations_problem/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_backtracking/subset_sum_problem/index.html b/en/chapter_backtracking/subset_sum_problem/index.html index 34ea69cd1..a0c630930 100644 --- a/en/chapter_backtracking/subset_sum_problem/index.html +++ b/en/chapter_backtracking/subset_sum_problem/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_backtracking/summary/index.html b/en/chapter_backtracking/summary/index.html index 83b4c5749..8d551925c 100644 --- a/en/chapter_backtracking/summary/index.html +++ b/en/chapter_backtracking/summary/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_computational_complexity/index.html b/en/chapter_computational_complexity/index.html index a0c086023..d038be26d 100644 --- a/en/chapter_computational_complexity/index.html +++ b/en/chapter_computational_complexity/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_computational_complexity/iteration_and_recursion/index.html b/en/chapter_computational_complexity/iteration_and_recursion/index.html index a0534aeed..97f7c4b76 100644 --- a/en/chapter_computational_complexity/iteration_and_recursion/index.html +++ b/en/chapter_computational_complexity/iteration_and_recursion/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_computational_complexity/performance_evaluation/index.html b/en/chapter_computational_complexity/performance_evaluation/index.html index edbad7ae0..649e5f392 100644 --- a/en/chapter_computational_complexity/performance_evaluation/index.html +++ b/en/chapter_computational_complexity/performance_evaluation/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_computational_complexity/space_complexity/index.html b/en/chapter_computational_complexity/space_complexity/index.html index 94db00238..21b863c44 100644 --- a/en/chapter_computational_complexity/space_complexity/index.html +++ b/en/chapter_computational_complexity/space_complexity/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_computational_complexity/summary/index.html b/en/chapter_computational_complexity/summary/index.html index 13d1c55f8..6791efe7f 100644 --- a/en/chapter_computational_complexity/summary/index.html +++ b/en/chapter_computational_complexity/summary/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_computational_complexity/time_complexity/index.html b/en/chapter_computational_complexity/time_complexity/index.html index a2705fc28..ef8f85852 100644 --- a/en/chapter_computational_complexity/time_complexity/index.html +++ b/en/chapter_computational_complexity/time_complexity/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_data_structure/basic_data_types/index.html b/en/chapter_data_structure/basic_data_types/index.html index 99d5ce885..5a7fce88a 100644 --- a/en/chapter_data_structure/basic_data_types/index.html +++ b/en/chapter_data_structure/basic_data_types/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_data_structure/character_encoding/index.html b/en/chapter_data_structure/character_encoding/index.html index c37dd0253..3acf592bb 100644 --- a/en/chapter_data_structure/character_encoding/index.html +++ b/en/chapter_data_structure/character_encoding/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_data_structure/classification_of_data_structure/index.html b/en/chapter_data_structure/classification_of_data_structure/index.html index 3da147c55..ebc5b453b 100644 --- a/en/chapter_data_structure/classification_of_data_structure/index.html +++ b/en/chapter_data_structure/classification_of_data_structure/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_data_structure/index.html b/en/chapter_data_structure/index.html index 81d08a627..d46491396 100644 --- a/en/chapter_data_structure/index.html +++ b/en/chapter_data_structure/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_data_structure/number_encoding/index.html b/en/chapter_data_structure/number_encoding/index.html index 7824b9a18..7692a5c39 100644 --- a/en/chapter_data_structure/number_encoding/index.html +++ b/en/chapter_data_structure/number_encoding/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_data_structure/summary/index.html b/en/chapter_data_structure/summary/index.html index 0fcdc7a73..be1a53ce0 100644 --- a/en/chapter_data_structure/summary/index.html +++ b/en/chapter_data_structure/summary/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_divide_and_conquer/binary_search_recur/index.html b/en/chapter_divide_and_conquer/binary_search_recur/index.html index 2027a8c46..3c68d57be 100644 --- a/en/chapter_divide_and_conquer/binary_search_recur/index.html +++ b/en/chapter_divide_and_conquer/binary_search_recur/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_divide_and_conquer/build_binary_tree_problem/index.html b/en/chapter_divide_and_conquer/build_binary_tree_problem/index.html index f4659f3fc..cd25d44b7 100644 --- a/en/chapter_divide_and_conquer/build_binary_tree_problem/index.html +++ b/en/chapter_divide_and_conquer/build_binary_tree_problem/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_divide_and_conquer/divide_and_conquer/index.html b/en/chapter_divide_and_conquer/divide_and_conquer/index.html index e0bac8264..aa3a68919 100644 --- a/en/chapter_divide_and_conquer/divide_and_conquer/index.html +++ b/en/chapter_divide_and_conquer/divide_and_conquer/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_divide_and_conquer/hanota_problem/index.html b/en/chapter_divide_and_conquer/hanota_problem/index.html index 7b28c5321..7247b9c56 100644 --- a/en/chapter_divide_and_conquer/hanota_problem/index.html +++ b/en/chapter_divide_and_conquer/hanota_problem/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_divide_and_conquer/index.html b/en/chapter_divide_and_conquer/index.html index 466d61327..0b64e8b90 100644 --- a/en/chapter_divide_and_conquer/index.html +++ b/en/chapter_divide_and_conquer/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_divide_and_conquer/summary/index.html b/en/chapter_divide_and_conquer/summary/index.html index 994cc46ad..42a713793 100644 --- a/en/chapter_divide_and_conquer/summary/index.html +++ b/en/chapter_divide_and_conquer/summary/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_dynamic_programming/dp_problem_features/index.html b/en/chapter_dynamic_programming/dp_problem_features/index.html index b50f0e97a..a79e823f5 100644 --- a/en/chapter_dynamic_programming/dp_problem_features/index.html +++ b/en/chapter_dynamic_programming/dp_problem_features/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_dynamic_programming/dp_solution_pipeline/index.html b/en/chapter_dynamic_programming/dp_solution_pipeline/index.html index b04fed9dd..70c302067 100644 --- a/en/chapter_dynamic_programming/dp_solution_pipeline/index.html +++ b/en/chapter_dynamic_programming/dp_solution_pipeline/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_dynamic_programming/edit_distance_problem/index.html b/en/chapter_dynamic_programming/edit_distance_problem/index.html index 2cb44b29b..d69b9654d 100644 --- a/en/chapter_dynamic_programming/edit_distance_problem/index.html +++ b/en/chapter_dynamic_programming/edit_distance_problem/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_dynamic_programming/index.html b/en/chapter_dynamic_programming/index.html index ba6f82e36..28b11728d 100644 --- a/en/chapter_dynamic_programming/index.html +++ b/en/chapter_dynamic_programming/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_dynamic_programming/intro_to_dynamic_programming/index.html b/en/chapter_dynamic_programming/intro_to_dynamic_programming/index.html index 0a490a806..425c50773 100644 --- a/en/chapter_dynamic_programming/intro_to_dynamic_programming/index.html +++ b/en/chapter_dynamic_programming/intro_to_dynamic_programming/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_dynamic_programming/knapsack_problem/index.html b/en/chapter_dynamic_programming/knapsack_problem/index.html index f72189a3e..a43b1d08f 100644 --- a/en/chapter_dynamic_programming/knapsack_problem/index.html +++ b/en/chapter_dynamic_programming/knapsack_problem/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_dynamic_programming/summary/index.html b/en/chapter_dynamic_programming/summary/index.html index 4493d3f79..7d8d08d11 100644 --- a/en/chapter_dynamic_programming/summary/index.html +++ b/en/chapter_dynamic_programming/summary/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_dynamic_programming/unbounded_knapsack_problem/index.html b/en/chapter_dynamic_programming/unbounded_knapsack_problem/index.html index a87a978ca..83ba0736b 100644 --- a/en/chapter_dynamic_programming/unbounded_knapsack_problem/index.html +++ b/en/chapter_dynamic_programming/unbounded_knapsack_problem/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_graph/graph/index.html b/en/chapter_graph/graph/index.html index 9dbc74e7c..cc9782976 100644 --- a/en/chapter_graph/graph/index.html +++ b/en/chapter_graph/graph/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_graph/graph_operations/index.html b/en/chapter_graph/graph_operations/index.html index db7431ea9..c63004c18 100644 --- a/en/chapter_graph/graph_operations/index.html +++ b/en/chapter_graph/graph_operations/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_graph/graph_traversal/index.html b/en/chapter_graph/graph_traversal/index.html index f332ce950..a61480884 100644 --- a/en/chapter_graph/graph_traversal/index.html +++ b/en/chapter_graph/graph_traversal/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_graph/index.html b/en/chapter_graph/index.html index 54772a216..993221d70 100644 --- a/en/chapter_graph/index.html +++ b/en/chapter_graph/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_graph/summary/index.html b/en/chapter_graph/summary/index.html index fe269f6e7..7f497da2d 100644 --- a/en/chapter_graph/summary/index.html +++ b/en/chapter_graph/summary/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_greedy/fractional_knapsack_problem/index.html b/en/chapter_greedy/fractional_knapsack_problem/index.html index caf7e47bd..95f75a114 100644 --- a/en/chapter_greedy/fractional_knapsack_problem/index.html +++ b/en/chapter_greedy/fractional_knapsack_problem/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_greedy/greedy_algorithm/index.html b/en/chapter_greedy/greedy_algorithm/index.html index 3a9dbb2cd..0a19e1d8e 100644 --- a/en/chapter_greedy/greedy_algorithm/index.html +++ b/en/chapter_greedy/greedy_algorithm/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_greedy/index.html b/en/chapter_greedy/index.html index 9f6d79b0f..eaca12393 100644 --- a/en/chapter_greedy/index.html +++ b/en/chapter_greedy/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_greedy/max_capacity_problem/index.html b/en/chapter_greedy/max_capacity_problem/index.html index a77222f60..e208343c7 100644 --- a/en/chapter_greedy/max_capacity_problem/index.html +++ b/en/chapter_greedy/max_capacity_problem/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_greedy/max_product_cutting_problem/index.html b/en/chapter_greedy/max_product_cutting_problem/index.html index 2cb704b38..d6153eff2 100644 --- a/en/chapter_greedy/max_product_cutting_problem/index.html +++ b/en/chapter_greedy/max_product_cutting_problem/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_greedy/summary/index.html b/en/chapter_greedy/summary/index.html index 6ea60aa60..b9263c45e 100644 --- a/en/chapter_greedy/summary/index.html +++ b/en/chapter_greedy/summary/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_hashing/hash_algorithm/index.html b/en/chapter_hashing/hash_algorithm/index.html index 717099b94..c557ca2a3 100644 --- a/en/chapter_hashing/hash_algorithm/index.html +++ b/en/chapter_hashing/hash_algorithm/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_hashing/hash_collision/index.html b/en/chapter_hashing/hash_collision/index.html index fbc521944..702d28dd4 100644 --- a/en/chapter_hashing/hash_collision/index.html +++ b/en/chapter_hashing/hash_collision/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_hashing/hash_map/index.html b/en/chapter_hashing/hash_map/index.html index 3e57668a2..d08793a45 100644 --- a/en/chapter_hashing/hash_map/index.html +++ b/en/chapter_hashing/hash_map/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_hashing/index.html b/en/chapter_hashing/index.html index 0448da6aa..d0e0718fe 100644 --- a/en/chapter_hashing/index.html +++ b/en/chapter_hashing/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_hashing/summary/index.html b/en/chapter_hashing/summary/index.html index 944a35e49..7d8e64bd4 100644 --- a/en/chapter_hashing/summary/index.html +++ b/en/chapter_hashing/summary/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_heap/build_heap/index.html b/en/chapter_heap/build_heap/index.html index db761df51..b161571ee 100644 --- a/en/chapter_heap/build_heap/index.html +++ b/en/chapter_heap/build_heap/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_heap/heap/index.html b/en/chapter_heap/heap/index.html index 352b381a2..03da6a1e9 100644 --- a/en/chapter_heap/heap/index.html +++ b/en/chapter_heap/heap/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_heap/index.html b/en/chapter_heap/index.html index dcb2c87d8..0ef22be7e 100644 --- a/en/chapter_heap/index.html +++ b/en/chapter_heap/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_heap/summary/index.html b/en/chapter_heap/summary/index.html index f7e3e61b0..774fff994 100644 --- a/en/chapter_heap/summary/index.html +++ b/en/chapter_heap/summary/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_heap/top_k/index.html b/en/chapter_heap/top_k/index.html index 2ca87bfb3..34aefe7c1 100644 --- a/en/chapter_heap/top_k/index.html +++ b/en/chapter_heap/top_k/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_hello_algo/index.html b/en/chapter_hello_algo/index.html index 7b56fa3b7..347d3e59c 100644 --- a/en/chapter_hello_algo/index.html +++ b/en/chapter_hello_algo/index.html @@ -63,8 +63,8 @@ - - + + diff --git a/en/chapter_introduction/algorithms_are_everywhere/index.html b/en/chapter_introduction/algorithms_are_everywhere/index.html index b990cb4d5..ea87c7cc1 100644 --- a/en/chapter_introduction/algorithms_are_everywhere/index.html +++ b/en/chapter_introduction/algorithms_are_everywhere/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_introduction/index.html b/en/chapter_introduction/index.html index 764412f97..09c437c5c 100644 --- a/en/chapter_introduction/index.html +++ b/en/chapter_introduction/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_introduction/summary/index.html b/en/chapter_introduction/summary/index.html index c34e1b2c6..b95a7a3fb 100644 --- a/en/chapter_introduction/summary/index.html +++ b/en/chapter_introduction/summary/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_introduction/what_is_dsa/index.html b/en/chapter_introduction/what_is_dsa/index.html index 3eb64bb6e..c8b72d20e 100644 --- a/en/chapter_introduction/what_is_dsa/index.html +++ b/en/chapter_introduction/what_is_dsa/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_preface/about_the_book/index.html b/en/chapter_preface/about_the_book/index.html index 437a6d365..dc21f115a 100644 --- a/en/chapter_preface/about_the_book/index.html +++ b/en/chapter_preface/about_the_book/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_preface/index.html b/en/chapter_preface/index.html index 88673de10..b22589d5b 100644 --- a/en/chapter_preface/index.html +++ b/en/chapter_preface/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_preface/suggestions/index.html b/en/chapter_preface/suggestions/index.html index 435f64f66..4b712a70e 100644 --- a/en/chapter_preface/suggestions/index.html +++ b/en/chapter_preface/suggestions/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_preface/summary/index.html b/en/chapter_preface/summary/index.html index 2e247a95b..82b868911 100644 --- a/en/chapter_preface/summary/index.html +++ b/en/chapter_preface/summary/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_reference/index.html b/en/chapter_reference/index.html index 556cd46c5..08c0cb397 100644 --- a/en/chapter_reference/index.html +++ b/en/chapter_reference/index.html @@ -63,8 +63,8 @@ - - + + diff --git a/en/chapter_searching/binary_search/index.html b/en/chapter_searching/binary_search/index.html index f46f01111..693c1162a 100644 --- a/en/chapter_searching/binary_search/index.html +++ b/en/chapter_searching/binary_search/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_searching/binary_search_edge/index.html b/en/chapter_searching/binary_search_edge/index.html index 81df69d28..4dafab7da 100644 --- a/en/chapter_searching/binary_search_edge/index.html +++ b/en/chapter_searching/binary_search_edge/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_searching/binary_search_insertion/index.html b/en/chapter_searching/binary_search_insertion/index.html index 91055f07a..f09090677 100644 --- a/en/chapter_searching/binary_search_insertion/index.html +++ b/en/chapter_searching/binary_search_insertion/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_searching/index.html b/en/chapter_searching/index.html index b477a1620..cf1e9740a 100644 --- a/en/chapter_searching/index.html +++ b/en/chapter_searching/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_searching/replace_linear_by_hashing/index.html b/en/chapter_searching/replace_linear_by_hashing/index.html index 1d7ea4e3c..7c8af80b4 100644 --- a/en/chapter_searching/replace_linear_by_hashing/index.html +++ b/en/chapter_searching/replace_linear_by_hashing/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_searching/searching_algorithm_revisited/index.html b/en/chapter_searching/searching_algorithm_revisited/index.html index 745e700d2..4ed4ec37c 100644 --- a/en/chapter_searching/searching_algorithm_revisited/index.html +++ b/en/chapter_searching/searching_algorithm_revisited/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_searching/summary/index.html b/en/chapter_searching/summary/index.html index bdfb39ae8..572f0634b 100644 --- a/en/chapter_searching/summary/index.html +++ b/en/chapter_searching/summary/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_sorting/bubble_sort/index.html b/en/chapter_sorting/bubble_sort/index.html index d29c3d2be..e2aaf73b2 100644 --- a/en/chapter_sorting/bubble_sort/index.html +++ b/en/chapter_sorting/bubble_sort/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_sorting/bucket_sort/index.html b/en/chapter_sorting/bucket_sort/index.html index b1af83014..d75191ddc 100644 --- a/en/chapter_sorting/bucket_sort/index.html +++ b/en/chapter_sorting/bucket_sort/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_sorting/counting_sort/index.html b/en/chapter_sorting/counting_sort/index.html index 6ba241143..0f1260607 100644 --- a/en/chapter_sorting/counting_sort/index.html +++ b/en/chapter_sorting/counting_sort/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_sorting/heap_sort/index.html b/en/chapter_sorting/heap_sort/index.html index c4f98fe03..433db0f50 100644 --- a/en/chapter_sorting/heap_sort/index.html +++ b/en/chapter_sorting/heap_sort/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_sorting/index.html b/en/chapter_sorting/index.html index 8d1c2511b..2bb2bc254 100644 --- a/en/chapter_sorting/index.html +++ b/en/chapter_sorting/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_sorting/insertion_sort/index.html b/en/chapter_sorting/insertion_sort/index.html index b9bcb27f7..3a59a34f1 100644 --- a/en/chapter_sorting/insertion_sort/index.html +++ b/en/chapter_sorting/insertion_sort/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_sorting/merge_sort/index.html b/en/chapter_sorting/merge_sort/index.html index d78f22221..237a15069 100644 --- a/en/chapter_sorting/merge_sort/index.html +++ b/en/chapter_sorting/merge_sort/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_sorting/quick_sort/index.html b/en/chapter_sorting/quick_sort/index.html index a53d60ea6..e7676a453 100644 --- a/en/chapter_sorting/quick_sort/index.html +++ b/en/chapter_sorting/quick_sort/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_sorting/radix_sort/index.html b/en/chapter_sorting/radix_sort/index.html index 8cbb059eb..d0d307f20 100644 --- a/en/chapter_sorting/radix_sort/index.html +++ b/en/chapter_sorting/radix_sort/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_sorting/selection_sort/index.html b/en/chapter_sorting/selection_sort/index.html index dfcda7323..7360d0ac5 100644 --- a/en/chapter_sorting/selection_sort/index.html +++ b/en/chapter_sorting/selection_sort/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_sorting/sorting_algorithm/index.html b/en/chapter_sorting/sorting_algorithm/index.html index 948ada92f..b7534553c 100644 --- a/en/chapter_sorting/sorting_algorithm/index.html +++ b/en/chapter_sorting/sorting_algorithm/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_sorting/summary/index.html b/en/chapter_sorting/summary/index.html index 6dc4209cf..6d6e42a57 100644 --- a/en/chapter_sorting/summary/index.html +++ b/en/chapter_sorting/summary/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_stack_and_queue/deque/index.html b/en/chapter_stack_and_queue/deque/index.html index 93b7f5dff..db52a2be7 100644 --- a/en/chapter_stack_and_queue/deque/index.html +++ b/en/chapter_stack_and_queue/deque/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_stack_and_queue/index.html b/en/chapter_stack_and_queue/index.html index d7ea9a6e3..479e802ab 100644 --- a/en/chapter_stack_and_queue/index.html +++ b/en/chapter_stack_and_queue/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_stack_and_queue/queue/index.html b/en/chapter_stack_and_queue/queue/index.html index 27d2d53a1..a1819f9c8 100644 --- a/en/chapter_stack_and_queue/queue/index.html +++ b/en/chapter_stack_and_queue/queue/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_stack_and_queue/stack/index.html b/en/chapter_stack_and_queue/stack/index.html index 32043a0cc..d26f40b97 100644 --- a/en/chapter_stack_and_queue/stack/index.html +++ b/en/chapter_stack_and_queue/stack/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_stack_and_queue/summary/index.html b/en/chapter_stack_and_queue/summary/index.html index a37b2a813..e484de749 100644 --- a/en/chapter_stack_and_queue/summary/index.html +++ b/en/chapter_stack_and_queue/summary/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_tree/array_representation_of_tree/index.html b/en/chapter_tree/array_representation_of_tree/index.html index f7d0c1c32..e327bce2c 100644 --- a/en/chapter_tree/array_representation_of_tree/index.html +++ b/en/chapter_tree/array_representation_of_tree/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_tree/avl_tree/index.html b/en/chapter_tree/avl_tree/index.html index 06e1cb5cf..8360fbaee 100644 --- a/en/chapter_tree/avl_tree/index.html +++ b/en/chapter_tree/avl_tree/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_tree/binary_search_tree/index.html b/en/chapter_tree/binary_search_tree/index.html index 6c153ebe8..a0b8a20a3 100644 --- a/en/chapter_tree/binary_search_tree/index.html +++ b/en/chapter_tree/binary_search_tree/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_tree/binary_tree/index.html b/en/chapter_tree/binary_tree/index.html index 88333d1d8..4a00b4f93 100644 --- a/en/chapter_tree/binary_tree/index.html +++ b/en/chapter_tree/binary_tree/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_tree/binary_tree_traversal/index.html b/en/chapter_tree/binary_tree_traversal/index.html index b73c5ff0b..45222d559 100644 --- a/en/chapter_tree/binary_tree_traversal/index.html +++ b/en/chapter_tree/binary_tree_traversal/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_tree/index.html b/en/chapter_tree/index.html index 391d4bf72..e9299245b 100644 --- a/en/chapter_tree/index.html +++ b/en/chapter_tree/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/chapter_tree/summary/index.html b/en/chapter_tree/summary/index.html index d6ec82d79..58af139ec 100644 --- a/en/chapter_tree/summary/index.html +++ b/en/chapter_tree/summary/index.html @@ -65,8 +65,8 @@ - - + + diff --git a/en/index.html b/en/index.html index e33689fd3..a23952a92 100644 --- a/en/index.html +++ b/en/index.html @@ -19,9 +19,9 @@ - + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + +

紙の書籍

+

長い時間をかけて磨き上げた『Hello アルゴリズム』の紙の書籍が、ついに発売されました!今の気持ちは、次の一節で表せます:

+

風を追い月を追って立ち止まるな、草原の果てには春の山がある。

+ +

+

以下の動画では紙の書籍を紹介しており、私の考えもいくつか含まれています:

+
    +
  • データ構造とアルゴリズムを学ぶ重要性。
  • +
  • なぜ紙の書籍で Python を選んだのか。
  • +
  • 知識共有に対する理解。
  • +
+
+

新人 UP 主ですので、ぜひ応援と高評価・チャンネル登録をお願いします~ありがとうございます!

+
+
+ +
+ +

紙の書籍のスナップショット:

+

+

+

長所と短所

+

紙の書籍ならではの魅力を、簡単にまとめると次のとおりです:

+
    +
  • フルカラー印刷を採用し、本書の「アニメーション図解」の強みをそのまま活かせます。
  • +
  • 紙の素材にもこだわり、色彩を高い精度で再現しつつ、紙の書籍ならではの質感も残しています。
  • +
  • 紙の書籍版は Web 版よりも書式が整っており、たとえば図中の数式には斜体を用いています。
  • +
  • 価格を上げずに、マインドマップの折り込みページやしおりも付属します。
  • +
  • 紙の書籍、Web 版、PDF 版で内容は同期しており、自由に切り替えて読めます。
  • +
+
+

Tip

+

紙の書籍と Web 版を同期させるのは難しいため、細かな違いが生じる場合があります。ご了承ください!

+
+

もちろん、購入前に検討しておくべき点もいくつかあります:

+
    +
  • Python 言語を使用しているため、あなたの主言語と合わない可能性があります(Python は疑似コードと捉え、考え方の理解を重視してください)。
  • +
  • フルカラー印刷は図解やコードの読みやすさを大きく高める一方で、白黒印刷より価格はやや高くなります。
  • +
+
+

Tip

+

「印刷品質」と「価格」は、アルゴリズムにおける「時間効率」と「空間効率」のようなもので、両立は容易ではありません。そして私は、「印刷品質」は「時間効率」に当たるため、より重視すべきだと考えています。

+
+

購入リンク

+

紙の書籍に興味があれば、ぜひ一冊ご検討ください。新刊の 5 割引を用意していただきましたので、こちらのリンクをご覧いただくか、以下の QR コードをスキャンしてください:

+

+

あとがき

+

当初、私は紙の書籍出版に必要な作業量を甘く見ていて、オープンソースプロジェクトをきちんと保守していれば、紙の書籍版も何らかの自動化手段で生成できると思っていました。実際には、紙の書籍の制作フローとオープンソースプロジェクトの更新の仕組みには大きな違いがあり、その間をつなぐには多くの追加作業が必要でした。

+

一冊の本の初稿と出版基準を満たす完成稿との間には、なお大きな隔たりがあります。出版社(企画、編集、デザイン、マーケティングなど)と著者が力を合わせ、長い時間をかけて磨き上げていく必要があります。ここで、図霊の企画編集者である王軍花さん、そして人民郵電出版社と図霊コミュニティで本書の出版工程に携わってくださったすべての皆さまに感謝いたします!

+

この本があなたの助けになれば幸いです!

+ + + + + + + + + + + + + + + + + + + + + + +
ご意見、ご質問、ご提案があればぜひコメントしてください
+ + + + + + + +
+
+ + + + + +
+ + + +
+ +
+ + +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ja/chapter_preface/about_the_book/index.html b/ja/chapter_preface/about_the_book/index.html index a92e17f70..57497669f 100644 --- a/ja/chapter_preface/about_the_book/index.html +++ b/ja/chapter_preface/about_the_book/index.html @@ -6,7 +6,7 @@ - + @@ -39,7 +39,7 @@ - 0.1 この本について - Hello アルゴリズム + 0.1 本書について - Hello アルゴリズム @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。 @@ -152,7 +152,7 @@
- 0.1   この本について + 0.1   本書について
@@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -701,7 +701,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -729,7 +729,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -866,7 +866,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1143,7 +1143,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1171,7 +1171,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1674,7 +1674,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1696,7 +1696,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1718,7 +1718,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -2025,7 +2025,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2188,7 +2188,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2574,7 +2574,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2630,7 +2630,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2658,7 +2658,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -3196,7 +3196,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3252,7 +3252,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3280,7 +3280,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3473,7 +3473,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3501,7 +3501,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3642,7 +3642,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3670,7 +3670,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3698,7 +3698,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3726,7 +3726,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3919,7 +3919,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4164,7 +4164,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4376,47 +4376,48 @@ -

0.1   この本について

-

このオープンソースプロジェクトは、データ構造とアルゴリズムに関する無料で初心者にやさしいクラッシュコースの作成を目指しています。

+

0.1   本書について

+

本プロジェクトは、オープンソースで無料、かつ初心者にやさしいデータ構造とアルゴリズムの入門書を作ることを目的としています。

    -
  • アニメーション付きの図解、理解しやすい内容、滑らかな学習曲線により、初心者がデータ構造とアルゴリズムの「知識マップ」を探索するのに役立ちます。
  • -
  • ワンクリックでコードを実行できるため、読者のプログラミングスキルの向上と、アルゴリズムの動作原理およびデータ構造の基礎実装の理解に役立ちます。
  • -
  • 教えることによる学習を促進し、質問や洞察の共有を自由に行ってください。議論を通じて一緒に成長しましょう。
  • +
  • 全編でアニメーション付きの図解を採用し、内容は明快で理解しやすく、学習曲線もなだらかで、初心者がデータ構造とアルゴリズムの知識地図を探求できるよう導きます。
  • +
  • ソースコードはワンクリックで実行でき、読者が演習を通じてプログラミング能力を高め、アルゴリズムの動作原理とデータ構造の内部実装を理解する助けとなります。
  • +
  • 読者どうしの助け合いによる学習を推奨しており、コメント欄で質問や見解を共有し、対話と議論を通じてともに成長していくことを歓迎します。

0.1.1   対象読者

-

もしあなたがアルゴリズムに触れたばかりで経験が限られている場合、またはアルゴリズムである程度の経験を積んでいても、データ構造とアルゴリズムについて曖昧な理解しかなく、常に「分かった」と「うーん」の間を行き来している場合、この本はあなたのためのものです!

-

すでにある程度の問題解決経験を積んでおり、ほとんどのタイプの問題に精通している場合、この本はアルゴリズムの知識体系を復習し整理するのに役立ちます。リポジトリのソースコードは「問題解決ツールキット」や「アルゴリズムチートシート」として使用できます。

-

もしあなたがアルゴリズムの専門家であれば、貴重な提案をいただくか、参加して協力していただければと思います。

+

もしあなたがアルゴリズム初心者で、これまでアルゴリズムに触れたことがない、あるいはすでに多少の問題演習の経験はあるものの、データ構造とアルゴリズムについてはまだ曖昧な理解にとどまり、できるかできないかの間を行き来しているなら、本書はまさにあなたのために作られています!

+

もしすでに一定量の問題演習を積み、ほとんどの問題パターンに慣れているなら、本書はアルゴリズム知識体系の復習と整理に役立ちます。リポジトリのソースコードは「問題演習ツール集」や「アルゴリズム辞典」として活用できます。

+

もしあなたがアルゴリズムの「達人」なら、貴重なご提案をいただけることを楽しみにしています。あるいは一緒に執筆に参加してください。

前提条件

-

少なくとも一つのプログラミング言語で簡単なコードを書いて読むことができる必要があります。

+

少なくともいずれか一つの言語でのプログラミング基礎があり、簡単なコードを読んだり書いたりできる必要があります。

0.1.2   内容構成

-

本書の主な内容を下図に示します。

+

本書の主な内容は以下の図のとおりです。

    -
  • 計算量解析: データ構造とアルゴリズムを評価する側面と方法を探求します。時間計算量と空間計算量を導出する方法、および一般的なタイプと例を扱います。
  • -
  • データ構造: 基本的なデータ型、分類方法、定義、長所と短所、一般的な操作、タイプ、応用、および配列、連結リスト、スタック、キュー、ハッシュテーブル、木、ヒープ、グラフなどのデータ構造の実装方法に焦点を当てます。
  • -
  • アルゴリズム: アルゴリズムを定義し、その長所と短所、効率性、応用シナリオ、問題解決ステップについて議論し、検索、ソート、分割統治、バックトラッキング、動的プログラミング、貪欲アルゴリズムなど、さまざまなアルゴリズムのサンプル問題を含みます。
  • +
  • 計算量解析:データ構造とアルゴリズムを評価する観点と方法。時間計算量と空間計算量の求め方、代表的な種類、例など。
  • +
  • データ構造:基本データ型とデータ構造の分類方法。配列、連結リスト、スタック、キュー、ハッシュテーブル、木、ヒープ、グラフなどのデータ構造の定義、長所と短所、基本操作、代表的な種類、典型的な応用、実装方法など。
  • +
  • アルゴリズム:探索、ソート、分割統治、バックトラッキング、動的計画法、貪欲法などのアルゴリズムの定義、長所と短所、効率、適用場面、問題を解く手順、例題など。

本書の主な内容

図 0-1   本書の主な内容

0.1.3   謝辞

-

本書は、オープンソースコミュニティの多数の貢献者による共同の努力のもと、継続的に改善されています。時間と労力を注いで執筆に携わってくださったすべての方々に感謝します。貢献者は以下のとおりです(GitHub により自動生成された順序):krahets、coderonion、Gonglja、nuomi1、Reanon、justin-tse、hpstory、danielsss、curtishd、night-cruise、S-N-O-R-L-A-X、rongyi、msk397、gvenusleo、khoaxuantu、rivertwilight、K3v123、gyt95、zhuoqinyue、yuelinxin、Zuoxun、mingXta、Phoenix0415、FangYuan33、GN-Yu、longsizhuo、IsChristina、xBLACKICEx、guowei-gong、Cathay-Chen、pengchzn、QiLOL、magentaqin、hello-ikun、JoseHung、qualifier1024、thomasq0、sunshinesDL、L-Super、Guanngxu、Transmigration-zhou、WSL0809、Slone123c、lhxsm、yuan0221、what-is-me、Shyam-Chen、theNefelibatas、longranger2、codeberg-user、xiongsp、JeffersonHuang、prinpal、seven1240、Wonderdch、malone6、xiaomiusa87、gaofer、bluebean-cloud、a16su、SamJin98、hongyun-robot、nanlei、XiaChuerwu、yd-j、iron-irax、mgisr、steventimes、junminhong、heshuyue、danny900714、MolDuM、Nigh、Dr-XYZ、XC-Zero、reeswell、PXG-XPG、NI-SW、Horbin-Magician、Enlightenus、YangXuanyi、beatrix-chan、DullSword、xjr7670、jiaxianhua、qq909244296、iStig、boloboloda、hts0000、gledfish、wenjianmin、keshida、kilikilikid、lclc6、lwbaptx、linyejoe2、liuxjerry、llql1211、fbigm、echo1937、szu17dmy、dshlstarr、Yucao-cy、coderlef、czruby、bongbongbakudan、beintentional、ZongYangL、ZhongYuuu、ZhongGuanbin、hezhizhen、linzeyan、ZJKung、luluxia、xb534、ztkuaikuai、yw-1021、ElaBosak233、baagod、zhouLion、yishangzhang、yi427、yanedie、yabo083、weibk、wangwang105、th1nk3r-ing、tao363、4yDX3906、syd168、sslmj2020、smilelsb、siqyka、selear、sdshaoda、Xi-Row、popozhu、nuquist19、noobcodemaker、XiaoK29、chadyi、lyl625760、lucaswangdev、0130w、shanghai-Jerry、EJackYang、Javesun99、eltociear、lipusheng、KNChiu、BlindTerran、ShiMaRing、lovelock、FreddieLi、FloranceYeh、fanchenggang、gltianwen、goerll、nedchu、curly210102、CuB3y0nd、KraHsu、CarrotDLaw、youshaoXG、bubble9um、Asashishi、Asa0oo0o0o、fanenr、eagleanurag、akshiterate、52coder、foursevenlove、KorsChen、GaochaoZhu、hopkings2008、yang-le、realwujing、Evilrabbit520、Umer-Jahangir、Turing-1024-Lee、Suremotoo、paoxiaomooo、Chieko-Seren、Allen-Scai、ymmmas、Risuntsy、Richard-Zhang1019、RafaelCaso、qingpeng9802、primexiao、Urbaner3、zhongfq、nidhoggfgg、MwumLi、CreatorMetaSky、martinx、ZnYang2018、hugtyftg、logan-qiu、psychelzh、Keynman、KeiichiKasai、KawaiiAsh。

-

この本のコードレビュー作業は、coderonion, Gonglja, gvenusleo, hpstory, justin‐tse, khoaxuantu, krahets, night-cruise, nuomi1, Reanon and rongyi(アルファベット順)によって完了されました。彼らの時間と努力に感謝し、様々な言語でのコードの標準化と統一性を確保してくださいました。

-

本書の繁体字中国語版は Shyam-Chen および Dr-XYZ によってレビューされ、英語版は yuelinxin、K3v123、QiLOL、Phoenix0415、SamJin98、yanedie、RafaelCaso、pengchzn、thomasq0、magentaqin によってレビューされ、日本語版は eltociear によってレビューされました。彼らの継続的な貢献のおかげで、本書はより幅広い読者層に提供することができています。ここに深く感謝いたします。

-

この本の制作過程において、多くの方々から貴重な支援をいただきました。これらに限定されませんが:

+

本書は、オープンソースコミュニティの多くの貢献者による共同の努力のもとで、継続的に改善されています。時間と労力を注いでくださったすべての執筆者の皆さんに感謝します。お名前は次のとおりです(GitHub により自動生成された順序):krahets、coderonion、Gonglja、nuomi1、Reanon、justin-tse、hpstory、danielsss、curtishd、night-cruise、S-N-O-R-L-A-X、rongyi、msk397、gvenusleo、khoaxuantu、rivertwilight、K3v123、gyt95、zhuoqinyue、yuelinxin、Zuoxun、mingXta、Phoenix0415、FangYuan33、GN-Yu、longsizhuo、IsChristina、xBLACKICEx、guowei-gong、Cathay-Chen、pengchzn、QiLOL、magentaqin、hello-ikun、JoseHung、qualifier1024、thomasq0、sunshinesDL、L-Super、Guanngxu、Transmigration-zhou、WSL0809、Slone123c、lhxsm、yuan0221、what-is-me、Shyam-Chen、theNefelibatas、longranger2、codeberg-user、xiongsp、JeffersonHuang、prinpal、seven1240、Wonderdch、malone6、xiaomiusa87、gaofer、bluebean-cloud、a16su、SamJin98、hongyun-robot、nanlei、XiaChuerwu、yd-j、iron-irax、mgisr、steventimes、junminhong、heshuyue、danny900714、MolDuM、Nigh、Dr-XYZ、XC-Zero、reeswell、PXG-XPG、NI-SW、Horbin-Magician、Enlightenus、YangXuanyi、beatrix-chan、DullSword、xjr7670、jiaxianhua、qq909244296、iStig、boloboloda、hts0000、gledfish、wenjianmin、keshida、kilikilikid、lclc6、lwbaptx、linyejoe2、liuxjerry、llql1211、fbigm、echo1937、szu17dmy、dshlstarr、Yucao-cy、coderlef、czruby、bongbongbakudan、beintentional、ZongYangL、ZhongYuuu、ZhongGuanbin、hezhizhen、linzeyan、ZJKung、luluxia、xb534、ztkuaikuai、yw-1021、ElaBosak233、baagod、zhouLion、yishangzhang、yi427、yanedie、yabo083、weibk、wangwang105、th1nk3r-ing、tao363、4yDX3906、syd168、sslmj2020、smilelsb、siqyka、selear、sdshaoda、Xi-Row、popozhu、nuquist19、noobcodemaker、XiaoK29、chadyi、lyl625760、lucaswangdev、0130w、shanghai-Jerry、EJackYang、Javesun99、eltociear、lipusheng、KNChiu、BlindTerran、ShiMaRing、lovelock、FreddieLi、FloranceYeh、fanchenggang、gltianwen、goerll、nedchu、curly210102、CuB3y0nd、KraHsu、CarrotDLaw、youshaoXG、bubble9um、Asashishi、Asa0oo0o0o、fanenr、eagleanurag、akshiterate、52coder、foursevenlove、KorsChen、GaochaoZhu、hopkings2008、yang-le、realwujing、Evilrabbit520、Umer-Jahangir、Turing-1024-Lee、Suremotoo、paoxiaomooo、Chieko-Seren、Allen-Scai、ymmmas、Risuntsy、Richard-Zhang1019、RafaelCaso、qingpeng9802、primexiao、Urbaner3、zhongfq、nidhoggfgg、MwumLi、CreatorMetaSky、martinx、ZnYang2018、hugtyftg、logan-qiu、psychelzh、Keynman、KeiichiKasai と KawaiiAsh。

+

本書のコードレビューは coderonion、curtishd、Gonglja、gvenusleo、hpstory、justin-tse、khoaxuantu、krahets、night-cruise、nuomi1、Reanon と rongyi によって行われました(アルファベット順)。彼らが費やしてくれた時間と労力に感謝します。各言語のコードの規範性と統一性が保たれているのは、まさに彼らのおかげです。

+

本書の繁体字中国語版は Shyam-Chen と Dr-XYZ がレビューし、英語版は yuelinxin、K3v123、QiLOL、Phoenix0415、SamJin98、yanedie、RafaelCaso、pengchzn、thomasq0 と magentaqin がレビューし、日本語版は eltociear がレビューしました。彼らの継続的な貢献があってこそ、本書はより幅広い読者に届けられています。感謝いたします。

+

本書の ePub 電子書籍生成ツールは zhongfq によって開発されました。彼の貢献に感謝します。読者により自由な読書方法を提供してくれました。

+

本書の執筆過程で、私は多くの方々の助けを得ました。

    -
  • 会社でのメンター、李熙博士に感謝します。ある会話で「早く行動しろ」と励ましていただき、この本を書く決意を固めることができました。
  • -
  • ガールフレンドのBubbleに感謝します。この本の最初の読者として、アルゴリズム初心者の視点から多くの貴重な提案をいただき、この本を初心者により適したものにしてくださいました。
  • -
  • Tengbao、Qibao、Feibaoに感謝します。この本のクリエイティブな名前を考えてくださり、みんなが初めて「Hello World!」を書いた時の素晴らしい思い出を呼び起こしてくれました。
  • -
  • Xiaoquanに感謝します。知的財産に関する専門的な支援を提供してくださり、このオープンソース本の開発において重要な役割を果たしてくださいました。
  • -
  • Sutongに感謝します。この本の美しいカバーとロゴをデザインしてくださり、私の要求で何度も修正を辛抱強く行ってくださいました。
  • -
  • @squidfunk に感謝します。執筆と組版の提案、および彼が開発したオープンソースドキュメントテーマ Material-for-MkDocs を提供してくださいました。
  • +
  • 会社での私の指導教員である李汐博士に感謝します。ある対話の中で「すぐに行動しよう」と励ましてくださり、この本を書く決意を固めることができました;
  • +
  • 私の恋人であり、本書の最初の読者でもある泡泡に感謝します。アルゴリズム初心者の視点から多くの貴重な提案をしてくれたおかげで、本書はより初心者に適したものになりました;
  • +
  • 腾宝、琦宝、飞宝が本書に創造性あふれる名前を付けてくれたことに感謝します。みんなが最初のコード行「Hello World!」を書いた美しい記憶を呼び起こしてくれました;
  • +
  • 校铨が知的財産の面で専門的な支援をしてくれたことに感謝します。これは本オープンソース書籍の改善に重要な役割を果たしました;
  • +
  • 苏潼が本書の美しい表紙と logo をデザインし、私の完璧主義につき合って何度も辛抱強く修正してくれたことに感謝します;
  • +
  • @squidfunk が組版に関する助言を提供してくれたこと、そして彼が開発したオープンソースのドキュメントテーマ Material-for-MkDocs に感謝します。
-

執筆の過程で、データ構造とアルゴリズムに関する多数の教科書や記事を深く研究しました。これらの作品は模範的なモデルとして機能し、この本の内容の正確性と品質を確保してくださいました。先人の方々の貴重な貢献に感謝いたします!

-

この本は、理論と実践を組み合わせた学習を提唱しており、この点で "Dive into Deep Learning" からインスピレーションを受けています。この優れた本をすべての読者に強くお勧めします。

-

継続的な支援と励ましにより、この興味深い仕事をすることを可能にしてくださった両親に心から感謝いたします

+

執筆の過程で、私はデータ構造とアルゴリズムに関する多くの教材や記事を読みました。これらの作品は本書に優れた手本を与え、本書の内容の正確性と品質を支えてくれました。ここに、すべての先生方と先人たちの卓越した貢献に感謝します!

+

本書は手と頭を同時に使う学習方法を提唱しています。この点で私は『手を動かして学ぶ深層学習』から大きな啓発を受けました。ここで読者の皆さんにこの優れた著作を強くお勧めします。

+

心から両親に感謝します。いつも支え励ましてくれたからこそ、私はこの興味深いことに取り組む機会を得ることができました

@@ -4439,7 +4440,7 @@ aria-label="フッター" diff --git a/ja/chapter_preface/index.html b/ja/chapter_preface/index.html index 75e47b475..f7941a88b 100644 --- a/ja/chapter_preface/index.html +++ b/ja/chapter_preface/index.html @@ -6,7 +6,7 @@ - + @@ -39,7 +39,7 @@ - 第 0 章 序文 - Hello アルゴリズム + 第 0 章 はじめに - Hello アルゴリズム @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。 @@ -152,7 +152,7 @@
- 第 0 章   序文 + 第 0 章   はじめに
@@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -620,7 +620,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -648,7 +648,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -785,7 +785,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1062,7 +1062,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1090,7 +1090,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1593,7 +1593,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1615,7 +1615,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1637,7 +1637,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1944,7 +1944,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2107,7 +2107,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2493,7 +2493,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2549,7 +2549,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2577,7 +2577,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -3115,7 +3115,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3171,7 +3171,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3199,7 +3199,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3392,7 +3392,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3420,7 +3420,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3561,7 +3561,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3589,7 +3589,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3617,7 +3617,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3645,7 +3645,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3838,7 +3838,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4083,7 +4083,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4273,12 +4273,12 @@ -

第 0 章   序文

-

序文

+

第 0 章   はじめに

+

はじめに

Abstract

-

アルゴリズムは美しい交響曲のようで、コードの一行一行がリズムのように流れています。

-

この本があなたの心の中で静かに響き、独特で深い旋律を残すことを願っています。

+

アルゴリズムは美しい交響曲のようであり、コードの一行一行が旋律のように流れていきます。

+

この本があなたの心の中でそっと響き、独自で深い旋律を残してくれることを願っています。

章の内容

    diff --git a/ja/chapter_preface/suggestions/index.html b/ja/chapter_preface/suggestions/index.html index de11aa15c..75d70273f 100644 --- a/ja/chapter_preface/suggestions/index.html +++ b/ja/chapter_preface/suggestions/index.html @@ -6,7 +6,7 @@ - + @@ -39,7 +39,7 @@ - 0.2 読み方 - Hello アルゴリズム + 0.2 本書の使い方 - Hello アルゴリズム @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。 @@ -152,7 +152,7 @@
    - 0.2   読み方 + 0.2   本書の使い方
    @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -544,7 +544,7 @@ - 0.2.1   記述規則 + 0.2.1   文章スタイルの約束 @@ -555,7 +555,7 @@ - 0.2.2   アニメーション図解による効率的学習 + 0.2.2   アニメーション図解で効率よく学ぶ @@ -566,7 +566,7 @@ - 0.2.3   コーディング実践による理解の深化 + 0.2.3   コード実践で理解を深める @@ -577,7 +577,7 @@ - 0.2.4   議論による共同学習 + 0.2.4   質問と議論を通じてともに成長する @@ -588,7 +588,7 @@ - 0.2.5   アルゴリズム学習パス + 0.2.5   アルゴリズム学習ロードマップ @@ -723,7 +723,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -751,7 +751,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -888,7 +888,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1165,7 +1165,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1193,7 +1193,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1696,7 +1696,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1718,7 +1718,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1740,7 +1740,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -2047,7 +2047,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2210,7 +2210,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2596,7 +2596,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2652,7 +2652,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2680,7 +2680,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -3218,7 +3218,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3274,7 +3274,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3302,7 +3302,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3495,7 +3495,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3523,7 +3523,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3664,7 +3664,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3692,7 +3692,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3720,7 +3720,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3748,7 +3748,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3941,7 +3941,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4186,7 +4186,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4332,7 +4332,7 @@ - 0.2.1   記述規則 + 0.2.1   文章スタイルの約束 @@ -4343,7 +4343,7 @@ - 0.2.2   アニメーション図解による効率的学習 + 0.2.2   アニメーション図解で効率よく学ぶ @@ -4354,7 +4354,7 @@ - 0.2.3   コーディング実践による理解の深化 + 0.2.3   コード実践で理解を深める @@ -4365,7 +4365,7 @@ - 0.2.4   議論による共同学習 + 0.2.4   質問と議論を通じてともに成長する @@ -4376,7 +4376,7 @@ - 0.2.5   アルゴリズム学習パス + 0.2.5   アルゴリズム学習ロードマップ @@ -4420,26 +4420,26 @@ -

    0.2   読み方

    +

    0.2   本書の使い方

    Tip

    -

    最良の読書体験のために、このセクションを通読することをお勧めします。

    +

    最適な読書体験を得るために、本節の内容を一通り読むことをおすすめします。

    -

    0.2.1   記述規則

    +

    0.2.1   文章スタイルの約束

      -
    • タイトルの後に「*」が付いた章は任意であり、比較的難易度の高い内容が含まれています。時間に制約がある場合は、これらをスキップすることをお勧めします。
    • -
    • 技術用語は太字(印刷版およびPDF版)または下線(Web版)で表示されます。例えば、配列などです。技術文書をより良く理解するために、これらに慣れることをお勧めします。
    • -
    • **太字のテキスト**は重要な内容や要約文を示し、特別な注意を払う価値があります。
    • -
    • 特定の意味を持つ単語や句は「引用符」で示され、曖昧さを避けます。
    • -
    • プログラミング言語間で一致しない用語については、この本はPythonに従います。例えば、nullを意味するためにNoneを使用します。
    • -
    • この本は、よりコンパクトなコンテンツレイアウトと引き換えに、プログラミング言語のコメント規約を部分的に無視しています。コメントは主に3つのタイプで構成されています:タイトルコメント、内容コメント、複数行コメント。
    • +
    • 見出しの後に * が付いているのは選読章で、内容は比較的難しめです。時間が限られている場合は、先に読み飛ばしてもかまいません。
    • +
    • 専門用語は太字(紙書籍版と PDF 版)または下線付き(Web 版)で示します。たとえば配列(array)のようなものです。文献を読む際に役立つため、覚えておくことをおすすめします。
    • +
    • 重要な内容やまとめの文は 太字 で示します。これらの文章には特に注意してください。
    • +
    • 特定の意味を持つ語句には“引用符”を付け、曖昧さを避けます。
    • +
    • プログラミング言語ごとに用語が一致しない場合、本書では Python を基準とします。たとえば、“空”を表すのに None を使います。
    • +
    • 本書では、よりコンパクトなレイアウトのために、言語ごとのコメント規約を一部省略しています。コメントは主に3種類あります。タイトルコメント、内容コメント、複数行コメントです。
    -
    +
    -
    """関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント"""
    +
    """タイトルコメント。関数、クラス、テストケースなどを示すために使います"""
     
    -# 詳細を説明するためのコメント
    +# 内容コメント。コードを詳しく説明するために使います
     
     """
     複数行
    @@ -4448,9 +4448,9 @@
     
    -
    /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */
    +
    /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */
     
    -// 詳細を説明するためのコメント
    +// 内容コメント。コードを詳しく説明するために使います
     
     /**
      * 複数行
    @@ -4459,9 +4459,9 @@
     
    -
    /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */
    +
    /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */
     
    -// 詳細を説明するためのコメント
    +// 内容コメント。コードを詳しく説明するために使います
     
     /**
      * 複数行
    @@ -4470,9 +4470,9 @@
     
    -
    /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */
    +
    /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */
     
    -// 詳細を説明するためのコメント
    +// 内容コメント。コードを詳しく説明するために使います
     
     /**
      * 複数行
    @@ -4481,9 +4481,9 @@
     
    -
    /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */
    +
    /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */
     
    -// 詳細を説明するためのコメント
    +// 内容コメント。コードを詳しく説明するために使います
     
     /**
      * 複数行
    @@ -4492,9 +4492,9 @@
     
    -
    /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */
    +
    /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */
     
    -// 詳細を説明するためのコメント
    +// 内容コメント。コードを詳しく説明するために使います
     
     /**
      * 複数行
    @@ -4503,9 +4503,9 @@
     
    -
    /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */
    +
    /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */
     
    -// 詳細を説明するためのコメント
    +// 内容コメント。コードを詳しく説明するために使います
     
     /**
      * 複数行
    @@ -4514,9 +4514,9 @@
     
    -
    /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */
    +
    /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */
     
    -// 詳細を説明するためのコメント
    +// 内容コメント。コードを詳しく説明するために使います
     
     /**
      * 複数行
    @@ -4525,9 +4525,9 @@
     
    -
    /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */
    +
    /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */
     
    -// 詳細を説明するためのコメント
    +// 内容コメント。コードを詳しく説明するために使います
     
     /**
      * 複数行
    @@ -4536,9 +4536,9 @@
     
    -
    /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */
    +
    /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */
     
    -// 詳細を説明するためのコメント
    +// 内容コメント。コードを詳しく説明するために使います
     
     /**
      * 複数行
    @@ -4547,9 +4547,9 @@
     
    -
    /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */
    +
    /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */
     
    -// 詳細を説明するためのコメント
    +// 内容コメント。コードを詳しく説明するために使います
     
     /**
      * 複数行
    @@ -4558,9 +4558,9 @@
     
    -
    /* 関数、クラス、テストサンプルなどをラベル付けするためのヘッダーコメント */
    +
    /* タイトルコメント。関数、クラス、テストケースなどを示すために使います */
     
    -// 詳細を説明するためのコメント
    +// 内容コメント。コードを詳しく説明するために使います
     
     /**
      * 複数行
    @@ -4568,51 +4568,63 @@
      */
     
    +
    +
    ### タイトルコメント。関数、クラス、テストケースなどを示すために使います ###
    +
    +# 内容コメント。コードを詳しく説明するために使います
    +
    +# 複数行
    +# コメント
    +
    -

    0.2.2   アニメーション図解による効率的学習

    -

    テキストと比較して、動画や画像は情報密度が高く、より構造化されており、理解しやすくなっています。この本では、重要で難しい概念は主にアニメーションと図解を通じて提示され、テキストは説明と補足として機能します。

    -

    下図に示すようなアニメーションや図解のある内容に遭遇した場合、図の理解を優先し、テキストを補足として、両方を統合して包括的な理解を得てください。

    +
    +

    0.2.2   アニメーション図解で効率よく学ぶ

    +

    文字と比べて、動画や画像は情報密度と構造化の度合いが高く、理解しやすいものです。本書では、重要かつ難解な知識は主にアニメーションによる図解で示し、文章は説明と補足を担います。

    +

    本書を読んでいて、ある内容に以下の図のようなアニメーション図解がある場合は、図を主、文章を従として、両方を合わせて理解してください。

    アニメーション図解の例

    図 0-2   アニメーション図解の例

    -

    0.2.3   コーディング実践による理解の深化

    -

    この本のソースコードはGitHubリポジトリでホストされています。下図に示すように、ソースコードにはテスト例が付属しており、ワンクリックで実行できます

    -

    時間に余裕がある場合は、自分でコードをタイプすることをお勧めします。時間がない場合は、少なくともすべてのコードを読んで実行してください。

    -

    コードを読むだけと比較して、コードを書くことは多くの場合、より多くの学習をもたらします。実践による学習こそが真の学習方法です。

    +

    0.2.3   コード実践で理解を深める

    +

    本書のサンプルコードは GitHub リポジトリ で管理されています。以下の図のように、ソースコードにはテストケースが付いており、ワンクリックで実行できます

    +

    時間に余裕があれば、コードを見ながら自分で一度書いてみることをおすすめします。学習時間が限られている場合でも、少なくともすべてのコードに目を通し、実行してください。

    +

    コードを読むのに比べて、書く過程のほうが得られるものは多いものです。手を動かしてこそ、本当に学んだことになります

    コード実行例

    図 0-3   コード実行例

    -

    コードを実行するための設定には、主に3つのステップが含まれます。

    -

    ステップ1:ローカルプログラミング環境をインストール。付録のチュートリアルに従ってインストールするか、すでにインストールされている場合はこのステップをスキップしてください。

    -

    ステップ2:コードリポジトリをクローンまたはダウンロードGitHubリポジトリを訪問してください。

    -

    Gitがインストールされている場合は、次のコマンドを使用してリポジトリをクローンします:

    -
    git clone https://github.com/krahets/hello-algo.git
    +

    コードを実行する前準備は主に3ステップです。

    +

    第1ステップ:ローカルのプログラミング環境をインストールする。付録のチュートリアルを参照してインストールしてください。すでにインストール済みであれば、この手順は省略できます。

    +

    第2ステップ:コードリポジトリをクローンまたはダウンロードするGitHub リポジトリ にアクセスしてください。すでに Git をインストールしている場合は、次のコマンドでこのリポジトリをクローンできます:

    +
    git clone https://github.com/krahets/hello-algo.git
     
    -

    または、下図に示す場所にある「Download ZIP」ボタンをクリックして、コードを圧縮ZIPファイルとして直接ダウンロードすることもできます。その後、ローカルで展開するだけです。

    +

    もちろん、以下の図に示す場所で“Download ZIP”ボタンをクリックし、コードの圧縮ファイルを直接ダウンロードしてローカルで展開することもできます。

    リポジトリのクローンとコードのダウンロード

    図 0-4   リポジトリのクローンとコードのダウンロード

    -

    ステップ3:ソースコードを実行。下図に示すように、上部にファイル名が記載されたコードブロックについては、リポジトリのcodesフォルダで対応するソースコードファイルを見つけることができます。これらのファイルはワンクリックで実行でき、不要なデバッグ時間を節約し、学習に集中できます。

    +

    第3ステップ:ソースコードを実行する。以下の図のように、上部にファイル名が表示されているコードブロックについては、リポジトリの codes フォルダ内に対応するソースコードファイルがあります。ソースコードファイルはワンクリックで実行できるため、不要なデバッグ時間を減らし、学習内容に集中できます。

    コードブロックと対応するソースコードファイル

    図 0-5   コードブロックと対応するソースコードファイル

    -

    0.2.4   議論による共同学習

    -

    この本を読んでいる間、学べなかった点を飛ばさないでください。コメントセクションで気軽に質問してください。喜んでお答えし、通常2日以内に回答できます。

    -

    下図に示すように、各章の下部にコメントセクションがあります。これらのコメントに注意を払うことをお勧めします。他の人が遭遇した問題を知ることで、知識のギャップを特定し、より深い思索を促すだけでなく、仲間の読者の質問に答えたり、洞察を共有したり、相互の向上を促進したりすることで寛大に貢献することも招待します。

    -

    コメントセクションの例

    -

    図 0-6   コメントセクションの例

    +

    ローカルでコードを実行するだけでなく、Web 版では Python コードの可視化実行にも対応していますpythontutor を利用)。以下の図のように、コードブロックの下にある“可視化実行”をクリックすると表示を展開し、アルゴリズムコードの実行過程を観察できます。また、“全画面表示”をクリックすると、より見やすい閲覧体験が得られます。

    +

    Python コードの可視化実行

    +

    図 0-6   Python コードの可視化実行

    -

    0.2.5   アルゴリズム学習パス

    -

    全体的に、データ構造とアルゴリズムをマスターする旅は3つの段階に分けることができます:

    +

    0.2.4   質問と議論を通じてともに成長する

    +

    本書を読んでいて、理解できていない知識点を安易に読み飛ばさないでください。コメント欄で気軽に質問してください。私と仲間たちが誠意をもって回答し、通常は 2 日以内に返信します。

    +

    以下の図のように、Web 版では各章の下部にコメント欄があります。ぜひコメント欄の内容にも目を通してください。一方では、みんなが直面した問題を知ることで知識の抜けを補い、より深い思考を促せます。もう一方では、ほかの仲間の質問にも積極的に答え、見解を共有し、互いの成長を助けてほしいと思います。

    +

    コメント欄の例

    +

    図 0-7   コメント欄の例

    + +

    0.2.5   アルゴリズム学習ロードマップ

    +

    全体として見ると、データ構造とアルゴリズムの学習過程は 3 つの段階に分けられます。

      -
    1. 段階1:アルゴリズムの入門。さまざまなデータ構造の特性と使用法に慣れ、異なるアルゴリズムの原理、プロセス、用途、効率について学ぶ必要があります。
    2. -
    3. 段階2:アルゴリズム問題の練習Sword for OfferLeetCode Hot 100などの人気のある問題から始めることをお勧めし、少なくとも100問を蓄積して主流のアルゴリズム問題に慣れることです。練習を始めると忘却が課題になる可能性がありますが、これは正常なことですのでご安心ください。「エビングハウスの忘却曲線」に従って問題を復習することができ、通常3〜5回の反復の後、それらを覚えることができるでしょう。
    4. -
    5. 段階3:知識体系の構築。学習の面では、アルゴリズムコラム記事、解法フレームワーク、アルゴリズム教科書を読んで知識体系を継続的に豊かにすることができます。練習の面では、トピック別分類、一つの問題に対する複数の解法、複数の問題に対する一つの解法など、高度な戦略を試すことができます。これらの戦略に関する洞察は、さまざまなコミュニティで見つけることができます。
    6. +
    7. 第 1 段階:アルゴリズム入門。さまざまなデータ構造の特徴と使い方に慣れ、異なるアルゴリズムの原理、流れ、用途、効率などを学ぶ必要があります。
    8. +
    9. 第 2 段階:アルゴリズム問題を解く。まずは人気の高い問題から取り組み、少なくとも 100 問は蓄積して、主流のアルゴリズム問題に慣れることをおすすめします。最初のうちは、“知識の忘却”が課題になるかもしれませんが、心配はいりません。これはごく自然なことです。“エビングハウスの忘却曲線”に沿って問題を復習すれば、通常は 3~5 回繰り返すことでしっかり記憶に定着します。おすすめの問題リストと学習計画は、この GitHub リポジトリ を参照してください。
    10. +
    11. 第 3 段階:知識体系を構築する。学習面では、アルゴリズムの連載記事、解法フレームワーク、教材などを読むことで、知識体系を継続的に充実させられます。問題演習の面では、トピック別分類、1 問多解、1 解多題といった発展的な戦略も試せます。関連する学習ノウハウは各コミュニティで見つけられます。
    -

    下図に示すように、この本は主に「段階1」をカバーしており、段階2と3により効率的に取り組むのに役立つことを目的としています。

    -

    アルゴリズム学習パス

    -

    図 0-7   アルゴリズム学習パス

    +

    以下の図のように、本書の内容は主に“第 1 段階”を扱っており、第 2 段階と第 3 段階の学習をより効率的に進める助けとなることを目的としています。

    +

    アルゴリズム学習ロードマップ

    +

    図 0-8   アルゴリズム学習ロードマップ

    diff --git a/ja/chapter_preface/summary/index.html b/ja/chapter_preface/summary/index.html index b0344e66e..d5aec77ea 100644 --- a/ja/chapter_preface/summary/index.html +++ b/ja/chapter_preface/summary/index.html @@ -6,7 +6,7 @@ - + @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
    @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -517,6 +517,24 @@ + + @@ -534,6 +552,37 @@ + + + + + @@ -630,7 +679,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -658,7 +707,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -795,7 +844,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1072,7 +1121,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1100,7 +1149,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1603,7 +1652,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1625,7 +1674,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1647,7 +1696,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1954,7 +2003,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2117,7 +2166,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2503,7 +2552,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2559,7 +2608,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2587,7 +2636,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -3125,7 +3174,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3181,7 +3230,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3209,7 +3258,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3402,7 +3451,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3430,7 +3479,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3571,7 +3620,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3599,7 +3648,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3627,7 +3676,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3655,7 +3704,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3848,7 +3897,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4093,7 +4142,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4229,6 +4278,25 @@ + + +
    @@ -4265,13 +4333,14 @@

    0.3   まとめ

    +

    1.   重要ポイントの振り返り

      -
    • この本の主な読者はアルゴリズムの初心者です。すでに基本的な知識をお持ちの場合、この本はアルゴリズムの知識を体系的に復習するのに役立ち、この本のソースコードは「コーディングツールキット」としても使用できます。
    • -
    • この本は3つの主要なセクション、計算量解析、データ構造、アルゴリズムで構成されており、この分野のほとんどのトピックをカバーしています。
    • -
    • アルゴリズムの初心者にとって、多くの回り道や一般的な落とし穴を避けるために、初期段階で入門書を読むことが重要です。
    • -
    • 本書内のアニメーションと図は通常、重要なポイントと難しい知識を紹介するために使用されます。本を読む際にはこれらにより多くの注意を払う必要があります。
    • -
    • 実践はプログラミングを学ぶ最良の方法です。ソースコードを実行し、自分でコードをタイプすることを強くお勧めします。
    • -
    • この本のWeb版の各章には議論セクションがあり、いつでも質問や洞察を共有することを歓迎します。
    • +
    • 本書の主な対象読者はアルゴリズム初学者です。すでにある程度の基礎がある場合でも、本書はアルゴリズム知識を体系的に振り返る助けとなり、書中のソースコードは「問題演習用ツール集」としても利用できます。
    • +
    • 本書の内容は主に計算量解析、データ構造、アルゴリズムの三部からなり、この分野の大部分のテーマを網羅しています。
    • +
    • アルゴリズム初心者にとって、学習初期の段階で入門書を読むことは非常に重要であり、多くの遠回りを避けられます。
    • +
    • 本書のアニメーション図解は通常、重要な知識や難しい知識を紹介するために用いられます。本書を読む際は、これらの内容により多く注意を払うべきです。
    • +
    • 実践はプログラミングを学ぶ最良の方法です。ソースコードを実行し、実際に自分でコードを書くことを強く勧めます。
    • +
    • 本書のWeb版の各章にはコメント欄が設けられており、疑問や見解をいつでも共有することを歓迎します。
    @@ -4319,7 +4388,7 @@ aria-label="フッター"
    @@ -339,7 +339,7 @@ - 序 + はじめに @@ -356,7 +356,7 @@ - 序 + はじめに @@ -616,7 +616,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -644,7 +644,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -781,7 +781,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1058,7 +1058,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1086,7 +1086,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1589,7 +1589,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1611,7 +1611,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1633,7 +1633,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1940,7 +1940,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2103,7 +2103,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2489,7 +2489,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2545,7 +2545,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2573,7 +2573,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -3111,7 +3111,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3167,7 +3167,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3195,7 +3195,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3388,7 +3388,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3416,7 +3416,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3557,7 +3557,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3585,7 +3585,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3613,7 +3613,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3641,7 +3641,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3834,7 +3834,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4079,7 +4079,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4256,11 +4256,11 @@

    [1] Thomas H. Cormen, et al. Introduction to Algorithms (3rd Edition).

    [2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1st Edition).

    [3] Robert Sedgewick, et al. Algorithms (4th Edition).

    -

    [4] Yan Weimin. Data Structures (C Language Version).

    -

    [5] Deng Junhui. Data Structures (C++ Language Version, Third Edition).

    -

    [6] Mark Allen Weiss, translated by Chen Yue. Data Structures and Algorithm Analysis in Java (Third Edition).

    -

    [7] Cheng Jie. Speaking of Data Structures.

    -

    [8] Wang Zheng. The Beauty of Data Structures and Algorithms.

    +

    [4] 严蔚敏. データ構造(C 言語版).

    +

    [5] 邓俊辉. データ構造(C++ 言語版、第3版).

    +

    [6] マーク・アレン・ワイス著,陈越訳. データ構造とアルゴリズム解析:Java言語による記述(第3版).

    +

    [7] 程杰. データ構造の話.

    +

    [8] 王争. データ構造とアルゴリズムの美.

    [9] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition).

    [10] Aston Zhang, et al. Dive into Deep Learning.

    diff --git a/ja/chapter_searching/binary_search/index.html b/ja/chapter_searching/binary_search/index.html index a9ca23dd5..67ba04e1e 100644 --- a/ja/chapter_searching/binary_search/index.html +++ b/ja/chapter_searching/binary_search/index.html @@ -6,7 +6,7 @@ - + @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
    @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1591,7 +1591,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1613,7 +1613,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1635,7 +1635,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1942,7 +1942,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2105,7 +2105,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2522,7 +2522,7 @@ - 10.1.1   区間表現方法 + 10.1.1   区間の表し方 @@ -2533,7 +2533,7 @@ - 10.1.2   利点と制限 + 10.1.2   利点と限界 @@ -2563,7 +2563,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2619,7 +2619,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2647,7 +2647,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -3185,7 +3185,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3241,7 +3241,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3269,7 +3269,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3462,7 +3462,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3490,7 +3490,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3631,7 +3631,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3659,7 +3659,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3687,7 +3687,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3715,7 +3715,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3908,7 +3908,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4153,7 +4153,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4299,7 +4299,7 @@ - 10.1.1   区間表現方法 + 10.1.1   区間の表し方 @@ -4310,7 +4310,7 @@ - 10.1.2   利点と制限 + 10.1.2   利点と限界 @@ -4355,30 +4355,30 @@

    10.1   二分探索

    -

    二分探索は分割統治戦略を用いる効率的な探索アルゴリズムです。配列内の要素の整列順序を利用し、各反復で探索区間を半分に減らしながら、目標要素が見つかるか探索区間が空になるまで続行します。

    +

    二分探索(binary search)は分割統治法に基づく効率的な探索アルゴリズムです。データが整列済みである性質を利用し、各ラウンドで探索範囲を半分に縮小し、目標要素を見つけるか探索区間が空になるまで続けます。

    Question

    -

    長さ\(n\)の配列numsが与えられ、要素は重複なしで昇順に配列されています。この配列内の要素targetのインデックスを見つけて返してください。配列に要素が含まれていない場合は\(-1\)を返してください。例を下図に示します。

    +

    長さ \(n\) の配列 nums が与えられます。要素は小さい順に並んでおり、重複しません。要素 target がこの配列内にある場合はそのインデックスを返し、含まれない場合は \(-1\) を返してください。例を次の図に示します。

    -

    Binary search example data

    -

    図 10-1   Binary search example data

    +

    二分探索の例

    +

    図 10-1   二分探索の例

    -

    下図に示すように、まず\(i = 0\)\(j = n - 1\)でポインタを初期化し、それぞれ配列の最初と最後の要素を指します。これらはまた全体の探索区間\([0, n - 1]\)を表します。角括弧は閉区間を示し、境界値自身も含むことに注意してください。

    -

    そして、以下の2つのステップをループで実行する可能性があります。

    +

    次の図に示すように、まずポインタ \(i = 0\)\(j = n - 1\) を初期化し、それぞれ配列の先頭要素と末尾要素を指すようにして、探索区間 \([0, n - 1]\) を表します。角括弧は閉区間を表し、境界値自体を含むことに注意してください。

    +

    次に、以下の 2 つの手順を繰り返します。

      -
    1. 中点インデックス\(m = \lfloor {(i + j) / 2} \rfloor\)を計算します。ここで\(\lfloor \: \rfloor\)は床関数を表します。
    2. -
    3. nums[m]targetの比較に基づいて、以下の3つのケースのうち1つを選択して実行します。
        -
      1. nums[m] < targetの場合、targetは区間\([m + 1, j]\)にあることを示すため、\(i = m + 1\)とします。
      2. -
      3. nums[m] > targetの場合、targetは区間\([i, m - 1]\)にあることを示すため、\(j = m - 1\)とします。
      4. -
      5. nums[m] = targetの場合、targetが見つかったことを示すため、インデックス\(m\)を返します。
      6. +
      7. 中央のインデックス \(m = \lfloor {(i + j) / 2} \rfloor\) を計算します。ここで \(\lfloor \: \rfloor\) は切り捨てを表します。
      8. +
      9. nums[m]target の大小関係を判定し、次の 3 つの場合に分かれます。
          +
        1. nums[m] < target のとき、target は区間 \([m + 1, j]\) にあるため、\(i = m + 1\) を実行します。
        2. +
        3. nums[m] > target のとき、target は区間 \([i, m - 1]\) にあるため、\(j = m - 1\) を実行します。
        4. +
        5. nums[m] = target のとき、target が見つかったので、インデックス \(m\) を返します。
      -

      配列に目標要素が含まれていない場合、探索区間は最終的に空になり、\(-1\)を返して終了します。

      +

      配列に目標要素が含まれない場合、探索区間は最終的に空まで縮小されます。このとき \(-1\) を返します。

      -

      Binary search process

      +

      二分探索の流れ

      binary_search_step2

      @@ -4400,152 +4400,342 @@
      -

      図 10-2   Binary search process

      +

      図 10-2   二分探索の流れ

      -

      \(i\)\(j\)が両方ともint型であるため、**\(i + j\)int型の範囲を超える可能性がある**ことは注目に値します。大きな数のオーバーフローを避けるため、通常は式\(m = \lfloor {i + (j - i) / 2} \rfloor\)を使用して中点を計算します。

      -

      コードは以下の通りです:

      +

      注意すべき点として、\(i\)\(j\) はどちらも int 型であるため、\(i + j\)int 型の範囲を超える可能性があります。大きな数によるオーバーフローを避けるため、通常は式 \(m = \lfloor {i + (j - i) / 2} \rfloor\) を用いて中点を計算します。

      +

      コードは次のとおりです。

      binary_search.py
      def binary_search(nums: list[int], target: int) -> int:
      -    """二分探索(両端閉区間)"""
      -    # 両端閉区間 [0, n-1] を初期化、すなわち i, j はそれぞれ配列の最初の要素と最後の要素を指す
      +    """二分探索(両閉区間)"""
      +    # 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す
           i, j = 0, len(nums) - 1
      -    # 検索区間が空になるまでループ(i > j のとき空)
      +    # ループし、探索区間が空になったら終了する(i > j で空)
           while i <= j:
      -        # 理論的には、Pythonの数値は無限に大きくなることができる(メモリサイズに依存)ため、大きな数のオーバーフローを考慮する必要はない
      -        m = i + (j - i) // 2  # 中点インデックス m を計算
      +        # 理論上、Python の数値は無限に大きくできるため(メモリ容量に依存)、大きな数のオーバーフローを考慮する必要はない
      +        m = (i + j) // 2  # 中点インデックス m を計算
               if nums[m] < target:
      -            i = m + 1  # この場合、target は区間 [m+1, j] にあることを示す
      +            i = m + 1  # この場合、target は区間 [m+1, j] にある
               elif nums[m] > target:
      -            j = m - 1  # この場合、target は区間 [i, m-1] にあることを示す
      +            j = m - 1  # この場合、target は区間 [i, m-1] にある
               else:
      -            return m  # ターゲット要素が見つかったため、そのインデックスを返す
      -    return -1  # ターゲット要素が見つからなかったため、-1 を返す
      +            return m  # 目標要素が見つかったらそのインデックスを返す
      +    return -1  # 目標要素が見つからなければ -1 を返す
       
      -
      binary_search.cpp
      /* 二分探索(両端閉区間) */
      +
      binary_search.cpp
      /* 二分探索(両閉区間) */
       int binarySearch(vector<int> &nums, int target) {
      -    // 両端閉区間[0, n-1]を初期化、すなわちi、jはそれぞれ配列の最初の要素と最後の要素を指す
      +    // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す
           int i = 0, j = nums.size() - 1;
      -    // 探索区間が空になるまでループ(i > jの時空になる)
      +    // ループし、探索区間が空になったら終了する(i > j で空)
           while (i <= j) {
      -        int m = i + (j - i) / 2; // 中点インデックスmを計算
      -        if (nums[m] < target)    // この状況はtargetが区間[m+1, j]にあることを示す
      +        int m = i + (j - i) / 2; // 中点インデックス m を計算
      +        if (nums[m] < target)    // この場合、target は区間 [m+1, j] にある
                   i = m + 1;
      -        else if (nums[m] > target) // この状況はtargetが区間[i, m-1]にあることを示す
      +        else if (nums[m] > target) // この場合、target は区間 [i, m-1] にある
                   j = m - 1;
      -        else // ターゲット要素が見つかったため、そのインデックスを返す
      +        else // 目標要素が見つかったらそのインデックスを返す
                   return m;
           }
      -    // ターゲット要素が見つからなかったため、-1を返す
      +    // 目標要素が見つからなければ -1 を返す
           return -1;
       }
       
      -
      binary_search.java
      /* 二分探索(両端閉区間) */
      +
      binary_search.java
      /* 二分探索(両閉区間) */
       int binarySearch(int[] nums, int target) {
      -    // 両端閉区間 [0, n-1] を初期化、すなわち i, j はそれぞれ配列の最初の要素と最後の要素を指す
      +    // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す
           int i = 0, j = nums.length - 1;
      -    // 探索区間が空になるまでループ(i > j のとき空)
      +    // ループし、探索区間が空になったら終了する(i > j で空)
           while (i <= j) {
               int m = i + (j - i) / 2; // 中点インデックス m を計算
      -        if (nums[m] < target) // この状況は target が区間 [m+1, j] にあることを示す
      +        if (nums[m] < target) // この場合、target は区間 [m+1, j] にある
                   i = m + 1;
      -        else if (nums[m] > target) // この状況は target が区間 [i, m-1] にあることを示す
      +        else if (nums[m] > target) // この場合、target は区間 [i, m-1] にある
                   j = m - 1;
      -        else // 目標要素を見つけたので、そのインデックスを返す
      +        else // 目標要素が見つかったらそのインデックスを返す
                   return m;
           }
      -    // 目標要素を見つけられなかったので、-1 を返す
      +    // 目標要素が見つからなければ -1 を返す
           return -1;
       }
       
      -
      binary_search.cs
      [class]{binary_search}-[func]{BinarySearch}
      +
      binary_search.cs
      /* 二分探索(両閉区間) */
      +int BinarySearch(int[] nums, int target) {
      +    // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す
      +    int i = 0, j = nums.Length - 1;
      +    // ループし、探索区間が空になったら終了する(i > j で空)
      +    while (i <= j) {
      +        int m = i + (j - i) / 2;   // 中点インデックス m を計算
      +        if (nums[m] < target)      // この場合、target は区間 [m+1, j] にある
      +            i = m + 1;
      +        else if (nums[m] > target) // この場合、target は区間 [i, m-1] にある
      +            j = m - 1;
      +        else                       // 目標要素が見つかったらそのインデックスを返す
      +            return m;
      +    }
      +    // 目標要素が見つからなければ -1 を返す
      +    return -1;
      +}
       
      -
      binary_search.go
      [class]{}-[func]{binarySearch}
      +
      binary_search.go
      /* 二分探索(両閉区間) */
      +func binarySearch(nums []int, target int) int {
      +    // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す
      +    i, j := 0, len(nums)-1
      +    // ループし、探索区間が空になったら終了する(i > j で空)
      +    for i <= j {
      +        m := i + (j-i)/2      // 中点インデックス m を計算
      +        if nums[m] < target { // この場合、target は区間 [m+1, j] にある
      +            i = m + 1
      +        } else if nums[m] > target { // この場合、target は区間 [i, m-1] にある
      +            j = m - 1
      +        } else { // 目標要素が見つかったらそのインデックスを返す
      +            return m
      +        }
      +    }
      +    // 目標要素が見つからなければ -1 を返す
      +    return -1
      +}
       
      -
      binary_search.swift
      [class]{}-[func]{binarySearch}
      +
      binary_search.swift
      /* 二分探索(両閉区間) */
      +func binarySearch(nums: [Int], target: Int) -> Int {
      +    // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す
      +    var i = nums.startIndex
      +    var j = nums.endIndex - 1
      +    // ループし、探索区間が空になったら終了する(i > j で空)
      +    while i <= j {
      +        let m = i + (j - i) / 2 // 中点インデックス m を計算
      +        if nums[m] < target { // この場合、target は区間 [m+1, j] にある
      +            i = m + 1
      +        } else if nums[m] > target { // この場合、target は区間 [i, m-1] にある
      +            j = m - 1
      +        } else { // 目標要素が見つかったらそのインデックスを返す
      +            return m
      +        }
      +    }
      +    // 目標要素が見つからなければ -1 を返す
      +    return -1
      +}
       
      -
      binary_search.js
      [class]{}-[func]{binarySearch}
      +
      binary_search.js
      /* 二分探索(両閉区間) */
      +function binarySearch(nums, target) {
      +    // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す
      +    let i = 0,
      +        j = nums.length - 1;
      +    // ループし、探索区間が空になったら終了する(i > j で空)
      +    while (i <= j) {
      +        // 中点インデックス `m` を計算し、`parseInt()` で切り捨てる
      +        const m = parseInt(i + (j - i) / 2);
      +        if (nums[m] < target)
      +            // この場合、target は区間 [m+1, j] にある
      +            i = m + 1;
      +        else if (nums[m] > target)
      +            // この場合、target は区間 [i, m-1] にある
      +            j = m - 1;
      +        else return m; // 目標要素が見つかったらそのインデックスを返す
      +    }
      +    // 目標要素が見つからなければ -1 を返す
      +    return -1;
      +}
       
      -
      binary_search.ts
      [class]{}-[func]{binarySearch}
      +
      binary_search.ts
      /* 二分探索(両閉区間) */
      +function binarySearch(nums: number[], target: number): number {
      +    // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す
      +    let i = 0,
      +        j = nums.length - 1;
      +    // ループし、探索区間が空になったら終了する(i > j で空)
      +    while (i <= j) {
      +        // 中点インデックス m を計算
      +        const m = Math.floor(i + (j - i) / 2);
      +        if (nums[m] < target) {
      +            // この場合、target は区間 [m+1, j] にある
      +            i = m + 1;
      +        } else if (nums[m] > target) {
      +            // この場合、target は区間 [i, m-1] にある
      +            j = m - 1;
      +        } else {
      +            // 目標要素が見つかったらそのインデックスを返す
      +            return m;
      +        }
      +    }
      +    return -1; // 目標要素が見つからなければ -1 を返す
      +}
       
      -
      binary_search.dart
      [class]{}-[func]{binarySearch}
      +
      binary_search.dart
      /* 二分探索(両閉区間) */
      +int binarySearch(List<int> nums, int target) {
      +  // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す
      +  int i = 0, j = nums.length - 1;
      +  // ループし、探索区間が空になったら終了する(i > j で空)
      +  while (i <= j) {
      +    int m = i + (j - i) ~/ 2; // 中点インデックス m を計算
      +    if (nums[m] < target) {
      +      // この場合、target は区間 [m+1, j] にある
      +      i = m + 1;
      +    } else if (nums[m] > target) {
      +      // この場合、target は区間 [i, m-1] にある
      +      j = m - 1;
      +    } else {
      +      // 目標要素が見つかったらそのインデックスを返す
      +      return m;
      +    }
      +  }
      +  // 目標要素が見つからなければ -1 を返す
      +  return -1;
      +}
       
      -
      binary_search.rs
      [class]{}-[func]{binary_search}
      +
      binary_search.rs
      /* 二分探索(両閉区間) */
      +fn binary_search(nums: &[i32], target: i32) -> i32 {
      +    // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す
      +    let mut i = 0;
      +    let mut j = nums.len() as i32 - 1;
      +    // ループし、探索区間が空になったら終了する(i > j で空)
      +    while i <= j {
      +        let m = i + (j - i) / 2; // 中点インデックス m を計算
      +        if nums[m as usize] < target {
      +            // この場合、target は区間 [m+1, j] にある
      +            i = m + 1;
      +        } else if nums[m as usize] > target {
      +            // この場合、target は区間 [i, m-1] にある
      +            j = m - 1;
      +        } else {
      +            // 目標要素が見つかったらそのインデックスを返す
      +            return m;
      +        }
      +    }
      +    // 目標要素が見つからなければ -1 を返す
      +    return -1;
      +}
       
      -
      binary_search.c
      [class]{}-[func]{binarySearch}
      +
      binary_search.c
      /* 二分探索(両閉区間) */
      +int binarySearch(int *nums, int len, int target) {
      +    // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す
      +    int i = 0, j = len - 1;
      +    // ループし、探索区間が空になったら終了する(i > j で空)
      +    while (i <= j) {
      +        int m = i + (j - i) / 2; // 中点インデックス m を計算
      +        if (nums[m] < target)    // この場合、target は区間 [m+1, j] にある
      +            i = m + 1;
      +        else if (nums[m] > target) // この場合、target は区間 [i, m-1] にある
      +            j = m - 1;
      +        else // 目標要素が見つかったらそのインデックスを返す
      +            return m;
      +    }
      +    // 目標要素が見つからなければ -1 を返す
      +    return -1;
      +}
       
      -
      binary_search.kt
      [class]{}-[func]{binarySearch}
      +
      binary_search.kt
      /* 二分探索(両閉区間) */
      +fun binarySearch(nums: IntArray, target: Int): Int {
      +    // 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す
      +    var i = 0
      +    var j = nums.size - 1
      +    // ループし、探索区間が空になったら終了する(i > j で空)
      +    while (i <= j) {
      +        val m = i + (j - i) / 2 // 中点インデックス m を計算
      +        if (nums[m] < target) // この場合、target は区間 [m+1, j] にある
      +            i = m + 1
      +        else if (nums[m] > target) // この場合、target は区間 [i, m-1] にある
      +            j = m - 1
      +        else  // 目標要素が見つかったらそのインデックスを返す
      +            return m
      +    }
      +    // 目標要素が見つからなければ -1 を返す
      +    return -1
      +}
       
      -
      binary_search.rb
      [class]{}-[func]{binary_search}
      +
      binary_search.rb
      ### 二分探索(両閉区間) ###
      +def binary_search(nums, target)
      +  # 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す
      +  i, j = 0, nums.length - 1
      +
      +  # ループし、探索区間が空になったら終了する(i > j で空)
      +  while i <= j
      +    # 理論上、Ruby の数値は無限に大きくできるため(メモリ容量に依存)、大きな数のオーバーフローを考慮する必要はない
      +    m = (i + j) / 2   # 中点インデックス m を計算
      +
      +    if nums[m] < target
      +      i = m + 1 # この場合、target は区間 [m+1, j] にある
      +    elsif nums[m] > target
      +      j = m - 1 # この場合、target は区間 [i, m-1] にある
      +    else
      +      return m  # 目標要素が見つかったらそのインデックスを返す
      +    end
      +  end
      +
      +  -1  # 目標要素が見つからなければ -1 を返す
      +end
       
      -

      時間計算量は\(O(\log n)\)です:二分ループにおいて、区間は各ラウンドで半分に減少するため、反復回数は\(\log_2 n\)となります。

      -

      空間計算量は\(O(1)\)です:ポインタ\(i\)\(j\)は定数サイズの空間を占有します。

      -

      10.1.1   区間表現方法

      -

      上記の閉区間の他に、もう一つの一般的な区間表現は「左閉右開」区間で、\([0, n)\)として定義され、左境界は自身を含み、右境界は含みません。この表現では、\(i = j\)のとき区間\([i, j)\)は空になります。

      -

      この表現に基づいて同じ機能を持つ二分探索アルゴリズムを実装できます:

      +
      +コードの可視化 +

      +

      +
      +

      時間計算量は \(O(\log n)\) :二分探索のループでは各ラウンドで区間が半分になるため、ループ回数は \(\log_2 n\) です。

      +

      空間計算量は \(O(1)\) :ポインタ \(i\)\(j\) に必要なのは定数サイズの空間だけです。

      +

      10.1.1   区間の表し方

      +

      上記の両閉区間のほかに、一般的な区間表現として「左閉右開」区間があり、\([0, n)\) と定義されます。つまり左端は含み、右端は含みません。この表現では、区間 \([i, j)\)\(i = j\) のとき空です。

      +

      この表現に基づいて、同じ機能を持つ二分探索アルゴリズムを実装できます。

      binary_search.py
      def binary_search_lcro(nums: list[int], target: int) -> int:
           """二分探索(左閉右開区間)"""
      -    # 左閉右開区間 [0, n) を初期化、すなわち i, j はそれぞれ配列の最初の要素と最後の要素+1を指す
      +    # 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す
           i, j = 0, len(nums)
      -    # 検索区間が空になるまでループ(i = j のとき空)
      +    # ループし、探索区間が空になったら終了する(i = j で空)
           while i < j:
      -        m = i + (j - i) // 2  # 中点インデックス m を計算
      +        m = (i + j) // 2  # 中点インデックス m を計算
               if nums[m] < target:
      -            i = m + 1  # この場合、target は区間 [m+1, j) にあることを示す
      +            i = m + 1  # この場合、target は区間 [m+1, j) にある
               elif nums[m] > target:
      -            j = m  # この場合、target は区間 [i, m) にあることを示す
      +            j = m  # この場合、target は区間 [i, m) にある
               else:
      -            return m  # ターゲット要素が見つかったため、そのインデックスを返す
      -    return -1  # ターゲット要素が見つからなかったため、-1 を返す
      +            return m  # 目標要素が見つかったらそのインデックスを返す
      +    return -1  # 目標要素が見つからなければ -1 を返す
       
      binary_search.cpp
      /* 二分探索(左閉右開区間) */
       int binarySearchLCRO(vector<int> &nums, int target) {
      -    // 左閉右開区間[0, n)を初期化、すなわちi、jはそれぞれ配列の最初の要素と最後の要素+1を指す
      +    // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す
           int i = 0, j = nums.size();
      -    // 探索区間が空になるまでループ(i = jの時空になる)
      +    // ループし、探索区間が空になったら終了する(i = j で空)
           while (i < j) {
      -        int m = i + (j - i) / 2; // 中点インデックスmを計算
      -        if (nums[m] < target)    // この状況はtargetが区間[m+1, j)にあることを示す
      +        int m = i + (j - i) / 2; // 中点インデックス m を計算
      +        if (nums[m] < target)    // この場合、target は区間 [m+1, j) にある
                   i = m + 1;
      -        else if (nums[m] > target) // この状況はtargetが区間[i, m)にあることを示す
      +        else if (nums[m] > target) // この場合、target は区間 [i, m) にある
                   j = m;
      -        else // ターゲット要素が見つかったため、そのインデックスを返す
      +        else // 目標要素が見つかったらそのインデックスを返す
                   return m;
           }
      -    // ターゲット要素が見つからなかったため、-1を返す
      +    // 目標要素が見つからなければ -1 を返す
           return -1;
       }
       
      @@ -4553,81 +4743,272 @@
      binary_search.java
      /* 二分探索(左閉右開区間) */
       int binarySearchLCRO(int[] nums, int target) {
      -    // 左閉右開区間 [0, n) を初期化、すなわち i, j はそれぞれ配列の最初の要素と最後の要素+1を指す
      +    // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す
           int i = 0, j = nums.length;
      -    // 探索区間が空になるまでループ(i = j のとき空)
      +    // ループし、探索区間が空になったら終了する(i = j で空)
           while (i < j) {
               int m = i + (j - i) / 2; // 中点インデックス m を計算
      -        if (nums[m] < target) // この状況は target が区間 [m+1, j) にあることを示す
      +        if (nums[m] < target) // この場合、target は区間 [m+1, j) にある
                   i = m + 1;
      -        else if (nums[m] > target) // この状況は target が区間 [i, m) にあることを示す
      +        else if (nums[m] > target) // この場合、target は区間 [i, m) にある
                   j = m;
      -        else // 目標要素を見つけたので、そのインデックスを返す
      +        else // 目標要素が見つかったらそのインデックスを返す
                   return m;
           }
      -    // 目標要素を見つけられなかったので、-1 を返す
      +    // 目標要素が見つからなければ -1 を返す
           return -1;
       }
       
      -
      binary_search.cs
      [class]{binary_search}-[func]{BinarySearchLCRO}
      +
      binary_search.cs
      /* 二分探索(左閉右開区間) */
      +int BinarySearchLCRO(int[] nums, int target) {
      +    // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す
      +    int i = 0, j = nums.Length;
      +    // ループし、探索区間が空になったら終了する(i = j で空)
      +    while (i < j) {
      +        int m = i + (j - i) / 2;   // 中点インデックス m を計算
      +        if (nums[m] < target)      // この場合、target は区間 [m+1, j) にある
      +            i = m + 1;
      +        else if (nums[m] > target) // この場合、target は区間 [i, m) にある
      +            j = m;
      +        else                       // 目標要素が見つかったらそのインデックスを返す
      +            return m;
      +    }
      +    // 目標要素が見つからなければ -1 を返す
      +    return -1;
      +}
       
      -
      binary_search.go
      [class]{}-[func]{binarySearchLCRO}
      +
      binary_search.go
      /* 二分探索(左閉右開区間) */
      +func binarySearchLCRO(nums []int, target int) int {
      +    // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す
      +    i, j := 0, len(nums)
      +    // ループし、探索区間が空になったら終了する(i = j で空)
      +    for i < j {
      +        m := i + (j-i)/2      // 中点インデックス m を計算
      +        if nums[m] < target { // この場合、target は区間 [m+1, j) にある
      +            i = m + 1
      +        } else if nums[m] > target { // この場合、target は区間 [i, m) にある
      +            j = m
      +        } else { // 目標要素が見つかったらそのインデックスを返す
      +            return m
      +        }
      +    }
      +    // 目標要素が見つからなければ -1 を返す
      +    return -1
      +}
       
      -
      binary_search.swift
      [class]{}-[func]{binarySearchLCRO}
      +
      binary_search.swift
      /* 二分探索(左閉右開区間) */
      +func binarySearchLCRO(nums: [Int], target: Int) -> Int {
      +    // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す
      +    var i = nums.startIndex
      +    var j = nums.endIndex
      +    // ループし、探索区間が空になったら終了する(i = j で空)
      +    while i < j {
      +        let m = i + (j - i) / 2 // 中点インデックス m を計算
      +        if nums[m] < target { // この場合、target は区間 [m+1, j) にある
      +            i = m + 1
      +        } else if nums[m] > target { // この場合、target は区間 [i, m) にある
      +            j = m
      +        } else { // 目標要素が見つかったらそのインデックスを返す
      +            return m
      +        }
      +    }
      +    // 目標要素が見つからなければ -1 を返す
      +    return -1
      +}
       
      -
      binary_search.js
      [class]{}-[func]{binarySearchLCRO}
      +
      binary_search.js
      /* 二分探索(左閉右開区間) */
      +function binarySearchLCRO(nums, target) {
      +    // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す
      +    let i = 0,
      +        j = nums.length;
      +    // ループし、探索区間が空になったら終了する(i = j で空)
      +    while (i < j) {
      +        // 中点インデックス `m` を計算し、`parseInt()` で切り捨てる
      +        const m = parseInt(i + (j - i) / 2);
      +        if (nums[m] < target)
      +            // この場合、target は区間 [m+1, j) にある
      +            i = m + 1;
      +        else if (nums[m] > target)
      +            // この場合、target は区間 [i, m) にある
      +            j = m;
      +        // 目標要素が見つかったらそのインデックスを返す
      +        else return m;
      +    }
      +    // 目標要素が見つからなければ -1 を返す
      +    return -1;
      +}
       
      -
      binary_search.ts
      [class]{}-[func]{binarySearchLCRO}
      +
      binary_search.ts
      /* 二分探索(左閉右開区間) */
      +function binarySearchLCRO(nums: number[], target: number): number {
      +    // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す
      +    let i = 0,
      +        j = nums.length;
      +    // ループし、探索区間が空になったら終了する(i = j で空)
      +    while (i < j) {
      +        // 中点インデックス m を計算
      +        const m = Math.floor(i + (j - i) / 2);
      +        if (nums[m] < target) {
      +            // この場合、target は区間 [m+1, j) にある
      +            i = m + 1;
      +        } else if (nums[m] > target) {
      +            // この場合、target は区間 [i, m) にある
      +            j = m;
      +        } else {
      +            // 目標要素が見つかったらそのインデックスを返す
      +            return m;
      +        }
      +    }
      +    return -1; // 目標要素が見つからなければ -1 を返す
      +}
       
      -
      binary_search.dart
      [class]{}-[func]{binarySearchLCRO}
      +
      binary_search.dart
      /* 二分探索(左閉右開区間) */
      +int binarySearchLCRO(List<int> nums, int target) {
      +  // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す
      +  int i = 0, j = nums.length;
      +  // ループし、探索区間が空になったら終了する(i = j で空)
      +  while (i < j) {
      +    int m = i + (j - i) ~/ 2; // 中点インデックス m を計算
      +    if (nums[m] < target) {
      +      // この場合、target は区間 [m+1, j) にある
      +      i = m + 1;
      +    } else if (nums[m] > target) {
      +      // この場合、target は区間 [i, m) にある
      +      j = m;
      +    } else {
      +      // 目標要素が見つかったらそのインデックスを返す
      +      return m;
      +    }
      +  }
      +  // 目標要素が見つからなければ -1 を返す
      +  return -1;
      +}
       
      -
      binary_search.rs
      [class]{}-[func]{binary_search_lcro}
      +
      binary_search.rs
      /* 二分探索(左閉右開区間) */
      +fn binary_search_lcro(nums: &[i32], target: i32) -> i32 {
      +    // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す
      +    let mut i = 0;
      +    let mut j = nums.len() as i32;
      +    // ループし、探索区間が空になったら終了する(i = j で空)
      +    while i < j {
      +        let m = i + (j - i) / 2; // 中点インデックス m を計算
      +        if nums[m as usize] < target {
      +            // この場合、target は区間 [m+1, j) にある
      +            i = m + 1;
      +        } else if nums[m as usize] > target {
      +            // この場合、target は区間 [i, m) にある
      +            j = m;
      +        } else {
      +            // 目標要素が見つかったらそのインデックスを返す
      +            return m;
      +        }
      +    }
      +    // 目標要素が見つからなければ -1 を返す
      +    return -1;
      +}
       
      -
      binary_search.c
      [class]{}-[func]{binarySearchLCRO}
      +
      binary_search.c
      /* 二分探索(左閉右開区間) */
      +int binarySearchLCRO(int *nums, int len, int target) {
      +    // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す
      +    int i = 0, j = len;
      +    // ループし、探索区間が空になったら終了する(i = j で空)
      +    while (i < j) {
      +        int m = i + (j - i) / 2; // 中点インデックス m を計算
      +        if (nums[m] < target)    // この場合、target は区間 [m+1, j) にある
      +            i = m + 1;
      +        else if (nums[m] > target) // この場合、target は区間 [i, m) にある
      +            j = m;
      +        else // 目標要素が見つかったらそのインデックスを返す
      +            return m;
      +    }
      +    // 目標要素が見つからなければ -1 を返す
      +    return -1;
      +}
       
      -
      binary_search.kt
      [class]{}-[func]{binarySearchLCRO}
      +
      binary_search.kt
      /* 二分探索(左閉右開区間) */
      +fun binarySearchLCRO(nums: IntArray, target: Int): Int {
      +    // 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す
      +    var i = 0
      +    var j = nums.size
      +    // ループし、探索区間が空になったら終了する(i = j で空)
      +    while (i < j) {
      +        val m = i + (j - i) / 2 // 中点インデックス m を計算
      +        if (nums[m] < target) // この場合、target は区間 [m+1, j) にある
      +            i = m + 1
      +        else if (nums[m] > target) // この場合、target は区間 [i, m) にある
      +            j = m
      +        else  // 目標要素が見つかったらそのインデックスを返す
      +            return m
      +    }
      +    // 目標要素が見つからなければ -1 を返す
      +    return -1
      +}
       
      -
      binary_search.rb
      [class]{}-[func]{binary_search_lcro}
      +
      binary_search.rb
      ### 二分探索(左閉右開区間) ###
      +def binary_search_lcro(nums, target)
      +  # 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す
      +  i, j = 0, nums.length
      +
      +  # ループし、探索区間が空になったら終了する(i = j で空)
      +  while i < j
      +    # 中点インデックス m を計算
      +    m = (i + j) / 2
      +
      +    if nums[m] < target
      +      i = m + 1 # この場合、target は区間 [m+1, j) にある
      +    elsif nums[m] > target
      +      j = m - 1 # この場合、target は区間 [i, m) にある
      +    else
      +      return m  # 目標要素が見つかったらそのインデックスを返す
      +    end
      +  end
      +
      +  -1  # 目標要素が見つからなければ -1 を返す
      +end
       
      -

      下図に示すように、2つの区間表現タイプにおいて、二分探索アルゴリズムの初期化、ループ条件、区間縮小操作が異なります。

      -

      「閉区間」表現では両方の境界が包含的であるため、ポインタ\(i\)\(j\)による区間縮小操作も対称的です。これによりエラーが発生しにくくなるため、一般的に「閉区間」アプローチの使用が推奨されます

      -

      Two types of interval definitions

      -

      図 10-3   Two types of interval definitions

      +
      +コードの可視化 +

      +

      +
      +

      次の図に示すように、2 種類の区間表現では、二分探索アルゴリズムの初期化、ループ条件、区間の縮小操作がそれぞれ異なります。

      +

      「両閉区間」の表現では左右の境界がどちらも閉区間として定義されるため、ポインタ \(i\) とポインタ \(j\) による区間縮小の操作も対称になります。このほうがミスをしにくいため、一般には「両閉区間」の書き方を推奨します

      +

      2 種類の区間定義

      +

      図 10-3   2 種類の区間定義

      -

      10.1.2   利点と制限

      -

      二分探索は時間と空間の両方の面で良好な性能を示します。

      +

      10.1.2   利点と限界

      +

      二分探索は時間と空間の両面で優れた性能を持ちます。

        -
      • 二分探索は時間効率が良いです。大きなデータセットでは、対数時間計算量が大きな利点を提供します。例えば、サイズ\(n = 2^{20}\)のデータセットが与えられた場合、線形探索は\(2^{20} = 1048576\)回の反復が必要ですが、二分探索は\(\log_2 2^{20} = 20\)回のループのみで済みます。
      • -
      • 二分探索には追加の空間が必要ありません。追加の空間に依存する探索アルゴリズム(ハッシュ探索など)と比較して、二分探索はより空間効率的です。
      • +
      • 二分探索は時間効率が高いです。データ量が大きい場合、対数時間計算量は大きな優位性を持ちます。たとえば、データサイズ \(n = 2^{20}\) のとき、線形探索では \(2^{20} = 1048576\) 回のループが必要ですが、二分探索では \(\log_2 2^{20} = 20\) 回で済みます。
      • +
      • 二分探索は追加の空間を必要としません。追加領域を要する探索アルゴリズム(たとえばハッシュ探索)と比べて、二分探索はより省メモリです。
      -

      しかし、二分探索は以下の懸念により、すべてのシナリオに適しているとは限りません。

      +

      しかし、二分探索があらゆる状況に適しているわけではなく、主な理由は次のとおりです。

        -
      • 二分探索はソート済みデータにのみ適用できます。未ソートのデータは二分探索を適用する前にソートする必要があり、ソートアルゴリズムは通常\(O(n \log n)\)の時間計算量を持つため、これは価値がないかもしれません。このコストは線形探索よりも高く、二分探索自体は言うまでもありません。頻繁な挿入があるシナリオでは、配列を順序に保つコストは非常に高く、特定の位置に新しい要素を挿入する時間計算量は\(O(n)\)です。
      • -
      • 二分探索は配列のみを使用できます。二分探索には非連続(ジャンプ)要素アクセスが必要で、これは連結リストでは非効率的です。そのため、連結リストや連結リストに基づくデータ構造はこのアルゴリズムに適していない可能性があります。
      • -
      • 線形探索は小さなデータセットでより良い性能を示します。線形探索では各反復で1つの判定操作のみが必要ですが、二分探索では1つの加算、1つの除算、1つから3つの判定操作、1つの加算(減算)を含み、合計4つから6つの操作が必要です。そのため、データサイズ\(n\)が小さい場合、線形探索は二分探索よりも高速です。
      • +
      • 二分探索は整列済みデータにしか適用できません。入力データが無秩序な場合、二分探索を使うためだけにソートするのは割に合いません。ソートアルゴリズムの時間計算量は通常 \(O(n \log n)\) であり、線形探索や二分探索よりも高いからです。要素を頻繁に挿入する場面では、配列の整列性を保つために特定位置へ挿入する必要があり、その時間計算量は \(O(n)\) と高コストです。
      • +
      • 二分探索は配列にしか適していません。二分探索では要素へ飛び飛びにアクセスする必要がありますが、連結リストでそのようなアクセスを行う効率は低いため、連結リストやそれを基に実装されたデータ構造には向きません。
      • +
      • データ量が小さい場合は線形探索のほうが高性能です。線形探索では各ラウンドで 1 回の比較だけで済みますが、二分探索では 1 回の加算、1 回の除算、1 ~ 3 回の比較、1 回の加算(減算)が必要で、合計 4 ~ 6 個の基本操作になります。したがって、データ量 \(n\) が小さいときは、線形探索のほうがかえって速くなります。
      @@ -4675,7 +5056,7 @@ aria-label="フッター"
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1591,7 +1591,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1613,7 +1613,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1635,7 +1635,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1942,7 +1942,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2105,7 +2105,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2493,7 +2493,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2578,7 +2578,7 @@ - 10.3.1   左境界を見つける + 10.3.1   左端境界を探す @@ -2589,19 +2589,19 @@ - 10.3.2   右境界を見つける + 10.3.2   右端境界を探す -
      @@ -4265,13 +4333,14 @@

      10.6   まとめ

      +

      1.   要点の振り返り

        -
      • 二分探索はデータの順序に依存し、探索区間を反復的に半分にすることで探索を実行します。入力データがソート済みである必要があり、配列または配列ベースのデータ構造にのみ適用可能です。
      • -
      • 無順序データセット内のエントリを見つけるには、総当たり探索が必要な場合があります。データ構造に基づいて異なる探索アルゴリズムを適用できます:線形探索は配列と連結リストに適しており、幅優先探索(BFS)と深さ優先探索(DFS)はグラフと木に適しています。これらのアルゴリズムは非常に汎用性が高く、データの前処理が不要ですが、\(O(n)\)という高い時間計算量を持ちます。
      • -
      • ハッシュ探索、木探索、二分探索は効率的な探索方法で、特定のデータ構造内で目標要素を迅速に特定できます。これらのアルゴリズムは非常に効率的で、時間計算量が\(O(\log n)\)または\(O(1)\)にまで達しますが、通常は追加のデータ構造を収容するために追加の空間が必要です。
      • -
      • 実際には、データ量、探索性能要件、データクエリと更新頻度などの要因を分析して、適切な探索方法を選択する必要があります。
      • -
      • 線形探索は小さなデータや頻繁に更新される(変動性の高い)データに理想的です。二分探索は大きくてソート済みのデータに適しています。ハッシュ探索は高いクエリ効率が必要で範囲クエリが不要なデータに適しています。木探索は順序を維持し、範囲クエリをサポートする必要がある大きな動的データに最も適しています。
      • -
      • 線形探索をハッシュ探索に置き換えることは、実行時性能を最適化する一般的な戦略で、時間計算量を\(O(n)\)から\(O(1)\)に削減します。
      • +
      • 二分探索はデータの順序性に依存し、ループによって探索区間を半分ずつ縮小しながら探索を行う。入力データがソート済みであることを前提とし、配列または配列ベースで実装されたデータ構造にのみ適用できる。
      • +
      • 総当たり探索はデータ構造を走査してデータを特定する。線形探索は配列と連結リストに適しており、幅優先探索と深さ優先探索はグラフと木に適している。この種のアルゴリズムは汎用性が高く、データの前処理を必要としないが、時間計算量 \(O(n)\) は高い。
      • +
      • ハッシュ探索、木探索、二分探索は高効率な探索手法であり、特定のデータ構造内で目的の要素を高速に特定できる。この種のアルゴリズムは効率が高く、時間計算量は \(O(\log n)\) あるいは \(O(1)\) に達するが、通常は追加のデータ構造を必要とする。
      • +
      • 実際には、データ規模、探索性能の要件、データの問い合わせ頻度や更新頻度などの要因を具体的に分析し、そのうえで適切な探索手法を選択する必要がある。
      • +
      • 線形探索は小規模または頻繁に更新されるデータに適している。二分探索は大規模でソート済みのデータに適している。ハッシュ探索は問い合わせ効率への要求が高く、範囲検索を必要としないデータに適している。木探索は順序の維持と範囲検索のサポートが必要な大規模動的データに適している。
      • +
      • ハッシュ探索で線形探索を置き換えることは、実行時間を最適化するための一般的な戦略であり、時間計算量を \(O(n)\) から \(O(1)\) へと下げられる。
      @@ -4295,7 +4364,7 @@ aria-label="フッター" diff --git a/ja/chapter_sorting/bubble_sort/index.html b/ja/chapter_sorting/bubble_sort/index.html index 4b9ea9ac6..b0662a216 100644 --- a/ja/chapter_sorting/bubble_sort/index.html +++ b/ja/chapter_sorting/bubble_sort/index.html @@ -6,7 +6,7 @@ - + @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1591,7 +1591,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1613,7 +1613,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1635,7 +1635,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1942,7 +1942,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2105,7 +2105,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2491,7 +2491,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2547,7 +2547,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2575,7 +2575,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -2839,7 +2839,7 @@ - 11.3.1   アルゴリズムプロセス + 11.3.1   アルゴリズムの流れ @@ -2861,7 +2861,7 @@ - 11.3.3   アルゴリズムの特性 + 11.3.3   アルゴリズムの特徴 @@ -3196,7 +3196,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3252,7 +3252,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3280,7 +3280,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3473,7 +3473,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3501,7 +3501,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3642,7 +3642,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3670,7 +3670,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3698,7 +3698,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3726,7 +3726,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3919,7 +3919,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4164,7 +4164,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4310,7 +4310,7 @@ - 11.3.1   アルゴリズムプロセス + 11.3.1   アルゴリズムの流れ @@ -4332,7 +4332,7 @@ - 11.3.3   アルゴリズムの特性 + 11.3.3   アルゴリズムの特徴 @@ -4377,12 +4377,12 @@

      11.3   バブルソート

      -

      バブルソートは、隣接する要素を継続的に比較し交換することで動作します。このプロセスは泡が底から上に上昇するようなものなので、「バブルソート」と名付けられました。

      -

      下図に示すように、バブリングプロセスは要素交換を使用してシミュレートできます:配列の左端から開始して右に移動し、隣接する要素の各ペアを比較します。左の要素が右の要素より大きい場合は、それらを交換します。横断後、最大要素は配列の右端にバブルアップします。

      +

      バブルソート(bubble sort)は、隣接する要素を繰り返し比較して交換することで整列を行います。この過程が泡のように下から上へ浮かび上がることから、バブルソートと呼ばれます。

      +

      次の図に示すように、バブル処理は要素の交換操作によってシミュレートできます。配列の最も左の端から右へ走査し、隣接する要素の大小を順に比較して、「左要素 > 右要素」であれば両者を交換します。走査が終わると、最大の要素は配列の最も右端へ移動します。

      -

      Simulating bubble process using element swap

      +

      要素の交換操作でバブル処理をシミュレート

      bubble_operation_step2

      @@ -4404,29 +4404,29 @@
      -

      図 11-4   Simulating bubble process using element swap

      +

      図 11-4   要素の交換操作でバブル処理をシミュレート

      -

      11.3.1   アルゴリズムプロセス

      -

      配列の長さを\(n\)とします。バブルソートのステップは下図に示されます:

      +

      11.3.1   アルゴリズムの流れ

      +

      配列の長さを \(n\) とすると、バブルソートの手順は次の図のとおりです。

        -
      1. まず、\(n\)個の要素に対して1回の「バブル」パスを実行し、最大要素を正しい位置に交換します
      2. -
      3. 次に、残りの\(n - 1\)個の要素に対して「バブル」パスを実行し、2番目に大きい要素を正しい位置に交換します
      4. -
      5. この方法で続行します;\(n - 1\)回のパスの後、最大\(n - 1\)個の要素が正しい位置に移動されます
      6. -
      7. 残りの唯一の要素は**必ず**最小であるため、**さらなる**ソートは必要ありません。この時点で、配列はソートされます。
      8. +
      9. まず、\(n\) 個の要素に対して「バブル処理」を行い、配列中の最大要素を正しい位置へ交換します
      10. +
      11. 次に、残りの \(n - 1\) 個の要素に対して「バブル処理」を行い、2 番目に大きい要素を正しい位置へ交換します
      12. +
      13. このようにして、\(n - 1\) 回の「バブル処理」を終えると、大きいほうから \(n - 1\) 個の要素がすべて正しい位置へ交換されます
      14. +
      15. 残った 1 つの要素は必ず最小要素なので、並べ替える必要はなく、これで配列のソートが完了します。
      -

      Bubble sort process

      -

      図 11-5   Bubble sort process

      +

      バブルソートの流れ

      +

      図 11-5   バブルソートの流れ

      -

      コード例は以下の通りです:

      +

      コード例は次のとおりです。

      bubble_sort.py
      def bubble_sort(nums: list[int]):
           """バブルソート"""
           n = len(nums)
      -    # 外側のループ:未ソート範囲は [0, i]
      +    # 外側のループ:未ソート区間は [0, i]
           for i in range(n - 1, 0, -1):
      -        # 内側のループ:未ソート範囲 [0, i] の最大要素を範囲の右端に移動
      +        # 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
               for j in range(i):
                   if nums[j] > nums[j + 1]:
                       # nums[j] と nums[j + 1] を交換
      @@ -4436,13 +4436,13 @@
       
      bubble_sort.cpp
      /* バブルソート */
       void bubbleSort(vector<int> &nums) {
      -    // 外側ループ:未ソート範囲は[0, i]
      +    // 外側のループ:未ソート区間は [0, i]
           for (int i = nums.size() - 1; i > 0; i--) {
      -        // 内側ループ:未ソート範囲[0, i]内の最大要素を範囲の右端に交換
      +        // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
               for (int j = 0; j < i; j++) {
                   if (nums[j] > nums[j + 1]) {
      -                // nums[j]とnums[j + 1]を交換
      -                // ここではstdのswapを使用
      +                // nums[j] と nums[j + 1] を交換する
      +                // ここでは std::swap() 関数を使用する
                       swap(nums[j], nums[j + 1]);
                   }
               }
      @@ -4453,9 +4453,9 @@
       
      bubble_sort.java
      /* バブルソート */
       void bubbleSort(int[] nums) {
      -    // 外側ループ: 未ソート範囲は [0, i]
      +    // 外側のループ:未ソート区間は [0, i]
           for (int i = nums.length - 1; i > 0; i--) {
      -        // 内側ループ: 未ソート範囲 [0, i] の最大要素を範囲の右端に交換
      +        // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
               for (int j = 0; j < i; j++) {
                   if (nums[j] > nums[j + 1]) {
                       // nums[j] と nums[j + 1] を交換
      @@ -4469,159 +4469,482 @@
       
      -
      bubble_sort.cs
      [class]{bubble_sort}-[func]{BubbleSort}
      +
      bubble_sort.cs
      /* バブルソート */
      +void BubbleSort(int[] nums) {
      +    // 外側のループ:未ソート区間は [0, i]
      +    for (int i = nums.Length - 1; i > 0; i--) {
      +        // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
      +        for (int j = 0; j < i; j++) {
      +            if (nums[j] > nums[j + 1]) {
      +                // nums[j] と nums[j + 1] を交換
      +                (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);
      +            }
      +        }
      +    }
      +}
       
      -
      bubble_sort.go
      [class]{}-[func]{bubbleSort}
      +
      bubble_sort.go
      /* バブルソート */
      +func bubbleSort(nums []int) {
      +    // 外側のループ:未ソート区間は [0, i]
      +    for i := len(nums) - 1; i > 0; i-- {
      +        // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
      +        for j := 0; j < i; j++ {
      +            if nums[j] > nums[j+1] {
      +                // nums[j] と nums[j + 1] を交換
      +                nums[j], nums[j+1] = nums[j+1], nums[j]
      +            }
      +        }
      +    }
      +}
       
      -
      bubble_sort.swift
      [class]{}-[func]{bubbleSort}
      +
      bubble_sort.swift
      /* バブルソート */
      +func bubbleSort(nums: inout [Int]) {
      +    // 外側のループ:未ソート区間は [0, i]
      +    for i in nums.indices.dropFirst().reversed() {
      +        // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
      +        for j in 0 ..< i {
      +            if nums[j] > nums[j + 1] {
      +                // nums[j] と nums[j + 1] を交換
      +                nums.swapAt(j, j + 1)
      +            }
      +        }
      +    }
      +}
       
      -
      bubble_sort.js
      [class]{}-[func]{bubbleSort}
      +
      bubble_sort.js
      /* バブルソート */
      +function bubbleSort(nums) {
      +    // 外側のループ:未ソート区間は [0, i]
      +    for (let i = nums.length - 1; i > 0; i--) {
      +        // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
      +        for (let j = 0; j < i; j++) {
      +            if (nums[j] > nums[j + 1]) {
      +                // nums[j] と nums[j + 1] を交換
      +                let tmp = nums[j];
      +                nums[j] = nums[j + 1];
      +                nums[j + 1] = tmp;
      +            }
      +        }
      +    }
      +}
       
      -
      bubble_sort.ts
      [class]{}-[func]{bubbleSort}
      +
      bubble_sort.ts
      /* バブルソート */
      +function bubbleSort(nums: number[]): void {
      +    // 外側のループ:未ソート区間は [0, i]
      +    for (let i = nums.length - 1; i > 0; i--) {
      +        // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
      +        for (let j = 0; j < i; j++) {
      +            if (nums[j] > nums[j + 1]) {
      +                // nums[j] と nums[j + 1] を交換
      +                let tmp = nums[j];
      +                nums[j] = nums[j + 1];
      +                nums[j + 1] = tmp;
      +            }
      +        }
      +    }
      +}
       
      -
      bubble_sort.dart
      [class]{}-[func]{bubbleSort}
      +
      bubble_sort.dart
      /* バブルソート */
      +void bubbleSort(List<int> nums) {
      +  // 外側のループ:未ソート区間は [0, i]
      +  for (int i = nums.length - 1; i > 0; i--) {
      +    // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
      +    for (int j = 0; j < i; j++) {
      +      if (nums[j] > nums[j + 1]) {
      +        // nums[j] と nums[j + 1] を交換
      +        int tmp = nums[j];
      +        nums[j] = nums[j + 1];
      +        nums[j + 1] = tmp;
      +      }
      +    }
      +  }
      +}
       
      -
      bubble_sort.rs
      [class]{}-[func]{bubble_sort}
      +
      bubble_sort.rs
      /* バブルソート */
      +fn bubble_sort(nums: &mut [i32]) {
      +    // 外側のループ:未ソート区間は [0, i]
      +    for i in (1..nums.len()).rev() {
      +        // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
      +        for j in 0..i {
      +            if nums[j] > nums[j + 1] {
      +                // nums[j] と nums[j + 1] を交換
      +                nums.swap(j, j + 1);
      +            }
      +        }
      +    }
      +}
       
      -
      bubble_sort.c
      [class]{}-[func]{bubbleSort}
      +
      bubble_sort.c
      /* バブルソート */
      +void bubbleSort(int nums[], int size) {
      +    // 外側のループ:未ソート区間は [0, i]
      +    for (int i = size - 1; i > 0; i--) {
      +        // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
      +        for (int j = 0; j < i; j++) {
      +            if (nums[j] > nums[j + 1]) {
      +                int temp = nums[j];
      +                nums[j] = nums[j + 1];
      +                nums[j + 1] = temp;
      +            }
      +        }
      +    }
      +}
       
      -
      bubble_sort.kt
      [class]{}-[func]{bubbleSort}
      +
      bubble_sort.kt
      /* バブルソート */
      +fun bubbleSort(nums: IntArray) {
      +    // 外側のループ:未ソート区間は [0, i]
      +    for (i in nums.size - 1 downTo 1) {
      +        // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
      +        for (j in 0..<i) {
      +            if (nums[j] > nums[j + 1]) {
      +                // nums[j] と nums[j + 1] を交換
      +                val temp = nums[j]
      +                nums[j] = nums[j + 1]
      +                nums[j + 1] = temp
      +            }
      +        }
      +    }
      +}
       
      -
      bubble_sort.rb
      [class]{}-[func]{bubble_sort}
      +
      bubble_sort.rb
      ### バブルソート ###
      +def bubble_sort(nums)
      +  n = nums.length
      +  # 外側のループ:未ソート区間は [0, i]
      +  for i in (n - 1).downto(1)
      +    # 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
      +    for j in 0...i
      +      if nums[j] > nums[j + 1]
      +        # nums[j] と nums[j + 1] を交換
      +        nums[j], nums[j + 1] = nums[j + 1], nums[j]
      +      end
      +    end
      +  end
      +end
       
      +
      +コードの可視化 +

      +

      +

      11.3.2   効率の最適化

      -

      「バブリング」のラウンド中に交換が発生しない場合、配列はすでにソートされているため、すぐに戻ることができます。これを検出するために、flag変数を追加できます;パスで交換が行われない場合は、フラグを設定して早期に戻ります。

      -

      この最適化があっても、バブルソートの最悪時間計算量と平均時間計算量は\(O(n^2)\)のままです。ただし、入力配列がすでにソートされている場合、最良ケース時間計算量は\(O(n)\)まで低くなる可能性があります。

      +

      ある回の「バブル処理」で交換操作が一度も行われなければ、配列はすでにソート済みであり、結果をそのまま返せることがわかります。したがって、この状況を検出するためのフラグ flag を追加し、発生した時点で直ちに返すようにできます。

      +

      最適化後も、バブルソートの最悪時間計算量と平均時間計算量は依然として \(O(n^2)\) です。ただし、入力配列が完全に整列済みであれば、最良時間計算量は \(O(n)\) に達します。

      bubble_sort.py
      def bubble_sort_with_flag(nums: list[int]):
      -    """バブルソート(フラグによる最適化)"""
      +    """バブルソート(フラグ最適化)"""
           n = len(nums)
      -    # 外側のループ:未ソート範囲は [0, i]
      +    # 外側のループ:未ソート区間は [0, i]
           for i in range(n - 1, 0, -1):
      -        flag = False  # フラグを初期化
      -        # 内側のループ:未ソート範囲 [0, i] の最大要素を範囲の右端に移動
      +        flag = False  # フラグを初期化する
      +        # 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
               for j in range(i):
                   if nums[j] > nums[j + 1]:
                       # nums[j] と nums[j + 1] を交換
                       nums[j], nums[j + 1] = nums[j + 1], nums[j]
      -                flag = True  # 要素を交換したことを記録
      +                flag = True  # 交換する要素を記録
               if not flag:
      -            break  # この回の「バブリング」で要素が交換されなかった場合、終了
      +            break  # このバブル処理で要素交換が一度もなければそのまま終了
       
      -
      bubble_sort.cpp
      /* バブルソート(フラグ最適化版)*/
      +
      bubble_sort.cpp
      /* バブルソート(フラグ最適化) */
       void bubbleSortWithFlag(vector<int> &nums) {
      -    // 外側ループ:未ソート範囲は[0, i]
      +    // 外側のループ:未ソート区間は [0, i]
           for (int i = nums.size() - 1; i > 0; i--) {
      -        bool flag = false; // フラグを初期化
      -        // 内側ループ:未ソート範囲[0, i]内の最大要素を範囲の右端に交換
      +        bool flag = false; // フラグを初期化する
      +        // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
               for (int j = 0; j < i; j++) {
                   if (nums[j] > nums[j + 1]) {
      -                // nums[j]とnums[j + 1]を交換
      -                // ここではstdのswapを使用
      +                // nums[j] と nums[j + 1] を交換する
      +                // ここでは std::swap() 関数を使用する
                       swap(nums[j], nums[j + 1]);
      -                flag = true; // 交換された要素を記録
      +                flag = true; // 交換する要素を記録
                   }
               }
               if (!flag)
      -            break; // この回の「バブリング」で要素が交換されなかった場合、終了
      +            break; // このバブル処理で要素交換が一度もなければそのまま終了
           }
       }
       
      -
      bubble_sort.java
      /* バブルソート(フラグによる最適化) */
      +
      bubble_sort.java
      /* バブルソート(フラグ最適化) */
       void bubbleSortWithFlag(int[] nums) {
      -    // 外側ループ: 未ソート範囲は [0, i]
      +    // 外側のループ:未ソート区間は [0, i]
           for (int i = nums.length - 1; i > 0; i--) {
      -        boolean flag = false; // フラグを初期化
      -        // 内側ループ: 未ソート範囲 [0, i] の最大要素を範囲の右端に交換
      +        boolean flag = false; // フラグを初期化する
      +        // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
               for (int j = 0; j < i; j++) {
                   if (nums[j] > nums[j + 1]) {
                       // nums[j] と nums[j + 1] を交換
                       int tmp = nums[j];
                       nums[j] = nums[j + 1];
                       nums[j + 1] = tmp;
      -                flag = true; // 交換された要素を記録
      +                flag = true; // 交換する要素を記録
                   }
               }
               if (!flag)
      -            break; // この「バブリング」ラウンドで要素が交換されなかった場合、終了
      +            break; // このバブル処理で要素交換が一度もなければそのまま終了
           }
       }
       
      -
      bubble_sort.cs
      [class]{bubble_sort}-[func]{BubbleSortWithFlag}
      +
      bubble_sort.cs
      /* バブルソート(フラグ最適化) */
      +void BubbleSortWithFlag(int[] nums) {
      +    // 外側のループ:未ソート区間は [0, i]
      +    for (int i = nums.Length - 1; i > 0; i--) {
      +        bool flag = false; // フラグを初期化する
      +        // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
      +        for (int j = 0; j < i; j++) {
      +            if (nums[j] > nums[j + 1]) {
      +                // nums[j] と nums[j + 1] を交換
      +                (nums[j + 1], nums[j]) = (nums[j], nums[j + 1]);
      +                flag = true;  // 交換する要素を記録
      +            }
      +        }
      +        if (!flag) break;     // このバブル処理で要素交換が一度もなければそのまま終了
      +    }
      +}
       
      -
      bubble_sort.go
      [class]{}-[func]{bubbleSortWithFlag}
      +
      bubble_sort.go
      /* バブルソート(フラグ最適化) */
      +func bubbleSortWithFlag(nums []int) {
      +    // 外側のループ:未ソート区間は [0, i]
      +    for i := len(nums) - 1; i > 0; i-- {
      +        flag := false // フラグを初期化する
      +        // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
      +        for j := 0; j < i; j++ {
      +            if nums[j] > nums[j+1] {
      +                // nums[j] と nums[j + 1] を交換
      +                nums[j], nums[j+1] = nums[j+1], nums[j]
      +                flag = true // 交換する要素を記録
      +            }
      +        }
      +        if flag == false { // このバブル処理で要素交換が一度もなければそのまま終了
      +            break
      +        }
      +    }
      +}
       
      -
      bubble_sort.swift
      [class]{}-[func]{bubbleSortWithFlag}
      +
      bubble_sort.swift
      /* バブルソート(フラグ最適化) */
      +func bubbleSortWithFlag(nums: inout [Int]) {
      +    // 外側のループ:未ソート区間は [0, i]
      +    for i in nums.indices.dropFirst().reversed() {
      +        var flag = false // フラグを初期化する
      +        for j in 0 ..< i {
      +            if nums[j] > nums[j + 1] {
      +                // nums[j] と nums[j + 1] を交換
      +                nums.swapAt(j, j + 1)
      +                flag = true // 交換する要素を記録
      +            }
      +        }
      +        if !flag { // このバブル処理で要素交換が一度もなければそのまま終了
      +            break
      +        }
      +    }
      +}
       
      -
      bubble_sort.js
      [class]{}-[func]{bubbleSortWithFlag}
      +
      bubble_sort.js
      /* バブルソート(フラグ最適化) */
      +function bubbleSortWithFlag(nums) {
      +    // 外側のループ:未ソート区間は [0, i]
      +    for (let i = nums.length - 1; i > 0; i--) {
      +        let flag = false; // フラグを初期化する
      +        // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
      +        for (let j = 0; j < i; j++) {
      +            if (nums[j] > nums[j + 1]) {
      +                // nums[j] と nums[j + 1] を交換
      +                let tmp = nums[j];
      +                nums[j] = nums[j + 1];
      +                nums[j + 1] = tmp;
      +                flag = true; // 交換する要素を記録
      +            }
      +        }
      +        if (!flag) break; // このバブル処理で要素交換が一度もなければそのまま終了
      +    }
      +}
       
      -
      bubble_sort.ts
      [class]{}-[func]{bubbleSortWithFlag}
      +
      bubble_sort.ts
      /* バブルソート(フラグ最適化) */
      +function bubbleSortWithFlag(nums: number[]): void {
      +    // 外側のループ:未ソート区間は [0, i]
      +    for (let i = nums.length - 1; i > 0; i--) {
      +        let flag = false; // フラグを初期化する
      +        // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
      +        for (let j = 0; j < i; j++) {
      +            if (nums[j] > nums[j + 1]) {
      +                // nums[j] と nums[j + 1] を交換
      +                let tmp = nums[j];
      +                nums[j] = nums[j + 1];
      +                nums[j + 1] = tmp;
      +                flag = true; // 交換する要素を記録
      +            }
      +        }
      +        if (!flag) break; // このバブル処理で要素交換が一度もなければそのまま終了
      +    }
      +}
       
      -
      bubble_sort.dart
      [class]{}-[func]{bubbleSortWithFlag}
      +
      bubble_sort.dart
      /* バブルソート(フラグ最適化) */
      +void bubbleSortWithFlag(List<int> nums) {
      +  // 外側のループ:未ソート区間は [0, i]
      +  for (int i = nums.length - 1; i > 0; i--) {
      +    bool flag = false; // フラグを初期化する
      +    // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
      +    for (int j = 0; j < i; j++) {
      +      if (nums[j] > nums[j + 1]) {
      +        // nums[j] と nums[j + 1] を交換
      +        int tmp = nums[j];
      +        nums[j] = nums[j + 1];
      +        nums[j + 1] = tmp;
      +        flag = true; // 交換する要素を記録
      +      }
      +    }
      +    if (!flag) break; // このバブル処理で要素交換が一度もなければそのまま終了
      +  }
      +}
       
      -
      bubble_sort.rs
      [class]{}-[func]{bubble_sort_with_flag}
      +
      bubble_sort.rs
      /* バブルソート(フラグ最適化) */
      +fn bubble_sort_with_flag(nums: &mut [i32]) {
      +    // 外側のループ:未ソート区間は [0, i]
      +    for i in (1..nums.len()).rev() {
      +        let mut flag = false; // フラグを初期化する
      +        // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
      +        for j in 0..i {
      +            if nums[j] > nums[j + 1] {
      +                // nums[j] と nums[j + 1] を交換
      +                nums.swap(j, j + 1);
      +                flag = true; // 交換する要素を記録
      +            }
      +        }
      +        if !flag {
      +            break; // このバブル処理で要素交換が一度もなければそのまま終了
      +        };
      +    }
      +}
       
      -
      bubble_sort.c
      [class]{}-[func]{bubbleSortWithFlag}
      +
      bubble_sort.c
      /* バブルソート(フラグ最適化) */
      +void bubbleSortWithFlag(int nums[], int size) {
      +    // 外側のループ:未ソート区間は [0, i]
      +    for (int i = size - 1; i > 0; i--) {
      +        bool flag = false;
      +        // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
      +        for (int j = 0; j < i; j++) {
      +            if (nums[j] > nums[j + 1]) {
      +                int temp = nums[j];
      +                nums[j] = nums[j + 1];
      +                nums[j + 1] = temp;
      +                flag = true;
      +            }
      +        }
      +        if (!flag)
      +            break;
      +    }
      +}
       
      -
      bubble_sort.kt
      [class]{}-[func]{bubbleSortWithFlag}
      +
      bubble_sort.kt
      /* バブルソート(フラグ最適化) */
      +fun bubbleSortWithFlag(nums: IntArray) {
      +    // 外側のループ:未ソート区間は [0, i]
      +    for (i in nums.size - 1 downTo 1) {
      +        var flag = false // フラグを初期化する
      +        // 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
      +        for (j in 0..<i) {
      +            if (nums[j] > nums[j + 1]) {
      +                // nums[j] と nums[j + 1] を交換
      +                val temp = nums[j]
      +                nums[j] = nums[j + 1]
      +                nums[j + 1] = temp
      +                flag = true // 交換する要素を記録
      +            }
      +        }
      +        if (!flag) break // このバブル処理で要素交換が一度もなければそのまま終了
      +    }
      +}
       
      -
      bubble_sort.rb
      [class]{}-[func]{bubble_sort_with_flag}
      +
      bubble_sort.rb
      ### バブルソート ###
      +def bubble_sort(nums)
      +  n = nums.length
      +  # 外側のループ:未ソート区間は [0, i]
      +  for i in (n - 1).downto(1)
      +    # 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
      +    for j in 0...i
      +      if nums[j] > nums[j + 1]
      +        # nums[j] と nums[j + 1] を交換
      +        nums[j], nums[j + 1] = nums[j + 1], nums[j]
      +      end
      +    end
      +  end
      +end
      +
      +# ## バブルソート(フラグ最適化)###
      +def bubble_sort_with_flag(nums)
      +  n = nums.length
      +  # 外側のループ:未ソート区間は [0, i]
      +  for i in (n - 1).downto(1)
      +    flag = false # フラグを初期化する
      +
      +    # 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
      +    for j in 0...i
      +      if nums[j] > nums[j + 1]
      +        # nums[j] と nums[j + 1] を交換
      +        nums[j], nums[j + 1] = nums[j + 1], nums[j]
      +        flag = true # 交換する要素を記録
      +      end
      +    end
      +
      +    break unless flag # このバブル処理で要素交換が一度もなければそのまま終了
      +  end
      +end
       
      -

      11.3.3   アルゴリズムの特性

      +
      +コードの可視化 +

      +

      +
      +

      11.3.3   アルゴリズムの特徴

        -
      • \(O(n^2)\)の時間計算量、適応ソート。 各「バブリング」ラウンドは長さ\(n - 1\)\(n - 2\)\(\dots\)\(2\)\(1\)の配列セグメントを横断し、合計は\((n - 1) n / 2\)となります。flag最適化により、配列がすでにソートされている場合、最良ケース時間計算量は\(O(n)\)に達する可能性があります。
      • -
      • \(O(1)\)の空間計算量、インプレースソート。 ポインタ\(i\)\(j\)によって定数量の追加空間のみが使用されます。
      • -
      • 安定ソート。 等しい要素は「バブリング」中に交換されないため、元の順序が保持され、これは安定ソートになります。
      • +
      • 時間計算量は \(O(n^2)\)、適応的ソート:各回の「バブル処理」で走査する配列の長さは順に \(n - 1\)\(n - 2\)\(\dots\)\(2\)\(1\) であり、その総和は \((n - 1) n / 2\) です。flag による最適化を導入すると、最良時間計算量は \(O(n)\) に達します。
      • +
      • 空間計算量は \(O(1)\)、インプレースソート:ポインタ \(i\)\(j\) は定数サイズの追加領域しか使用しません。
      • +
      • 安定ソート:「バブル処理」では等しい要素に出会っても交換しないためです。
      diff --git a/ja/chapter_sorting/bucket_sort/index.html b/ja/chapter_sorting/bucket_sort/index.html index c17400622..d23ede207 100644 --- a/ja/chapter_sorting/bucket_sort/index.html +++ b/ja/chapter_sorting/bucket_sort/index.html @@ -6,7 +6,7 @@ - + @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1591,7 +1591,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1613,7 +1613,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1635,7 +1635,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1942,7 +1942,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2105,7 +2105,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2491,7 +2491,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2547,7 +2547,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2575,7 +2575,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -2979,7 +2979,7 @@ - 11.8.1   アルゴリズムの過程 + 11.8.1   アルゴリズムの流れ @@ -2990,7 +2990,7 @@ - 11.8.2   アルゴリズムの特徴 + 11.8.2   アルゴリズムの特性 @@ -3001,7 +3001,7 @@ - 11.8.3   均等分散を達成する方法 + 11.8.3   均等な分配を実現するには @@ -3196,7 +3196,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3252,7 +3252,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3280,7 +3280,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3473,7 +3473,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3501,7 +3501,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3642,7 +3642,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3670,7 +3670,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3698,7 +3698,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3726,7 +3726,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3919,7 +3919,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4164,7 +4164,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4310,7 +4310,7 @@ - 11.8.1   アルゴリズムの過程 + 11.8.1   アルゴリズムの流れ @@ -4321,7 +4321,7 @@ - 11.8.2   アルゴリズムの特徴 + 11.8.2   アルゴリズムの特性 @@ -4332,7 +4332,7 @@ - 11.8.3   均等分散を達成する方法 + 11.8.3   均等な分配を実現するには @@ -4377,38 +4377,38 @@

      11.8   バケットソート

      -

      前述のソートアルゴリズムはすべて「比較ベースのソートアルゴリズム」で、値を比較することで要素をソートします。このようなソートアルゴリズムは \(O(n \log n)\) より良い時間計算量を持つことはできません。次に、線形時間計算量を達成できるいくつかの「非比較ソートアルゴリズム」について議論します。

      -

      バケットソートは分割統治戦略の典型的な応用です。一連の順序付けられたバケットを設定し、各バケットがデータの範囲を含み、入力データをこれらのバケットに均等に分散させることで動作します。そして、各バケット内のデータを個別にソートします。最後に、すべてのバケットからのソート済みデータを順次マージして最終結果を生成します。

      -

      11.8.1   アルゴリズムの過程

      -

      長さ \(n\) の配列で、\([0, 1)\) の範囲の浮動小数点数を考えてみます。バケットソートの過程は以下の図に示されています。

      +

      前述のいくつかのソートアルゴリズムは、いずれも「比較ベースのソートアルゴリズム」に属し、要素間の大小を比較することで整列を実現します。この種のソートアルゴリズムの時間計算量は \(O(n \log n)\) を超えられません。続いて、時間計算量が線形オーダーに達しうる「非比較ソートアルゴリズム」をいくつか見ていきます。

      +

      バケットソート(bucket sort)は分割統治戦略の典型的な応用です。大小関係をもつ複数のバケットを用意し、各バケットがあるデータ範囲に対応するようにして、データを各バケットへ均等に分配します。その後、各バケット内でそれぞれソートを行い、最後にバケットの順序に従ってすべてのデータを結合します。

      +

      11.8.1   アルゴリズムの流れ

      +

      長さ \(n\) の配列を考え、その要素は範囲 \([0, 1)\) の浮動小数点数であるとします。バケットソートの流れを以下の図に示します。

        -
      1. \(k\) 個のバケットを初期化し、\(n\) 個の要素をこれらの \(k\) 個のバケットに分散させます。
      2. -
      3. 各バケットを個別にソートします(プログラミング言語の組み込みソート関数を使用)。
      4. -
      5. 最小から最大のバケットの順序で結果をマージします。
      6. +
      7. \(k\) 個のバケットを初期化し、\(n\) 個の要素を \(k\) 個のバケットに分配します。
      8. +
      9. 各バケットに対してそれぞれソートを実行します(ここではプログラミング言語の組み込みソート関数を用います)。
      10. +
      11. バケットを小さい順にたどって結果を結合します。
      -

      バケットソートアルゴリズムの過程

      -

      図 11-13   バケットソートアルゴリズムの過程

      +

      バケットソートの流れ

      +

      図 11-13   バケットソートの流れ

      -

      コードは以下の通りです:

      +

      コードは以下のとおりです:

      bucket_sort.py
      def bucket_sort(nums: list[float]):
           """バケットソート"""
      -    # k = n/2 個のバケットを初期化、各バケットに平均2個の要素を配置することを期待
      +    # k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする
           k = len(nums) // 2
           buckets = [[] for _ in range(k)]
      -    # 1. 配列要素を各バケットに分散
      +    # 1. 配列要素を各バケットに振り分ける
           for num in nums:
      -        # 入力データ範囲は [0, 1)、num * k を使用してインデックス範囲 [0, k-1] にマッピング
      +        # 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する
               i = int(num * k)
               # num をバケット i に追加
               buckets[i].append(num)
      -    # 2. 各バケットをソート
      +    # 2. 各バケットをソートする
           for bucket in buckets:
      -        # 組み込みソート関数を使用、他のソートアルゴリズムに置き換えることも可能
      +        # 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい
               bucket.sort()
      -    # 3. バケットを走査して結果をマージ
      +    # 3. バケットを走査して結果を結合
           i = 0
           for bucket in buckets:
               for num in bucket:
      @@ -4419,22 +4419,22 @@
       
      bucket_sort.cpp
      /* バケットソート */
       void bucketSort(vector<float> &nums) {
      -    // k = n/2個のバケットを初期化、各バケットに2つの要素を割り当てることを期待
      +    // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする
           int k = nums.size() / 2;
           vector<vector<float>> buckets(k);
      -    // 1. 配列要素を各バケットに分配
      +    // 1. 配列要素を各バケットに振り分ける
           for (float num : nums) {
      -        // 入力データ範囲は[0, 1)、num * kを使用してインデックス範囲[0, k-1]にマップ
      +        // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する
               int i = num * k;
      -        // bucket_idxバケットに数値を追加
      +        // num をバケット bucket_idx に追加
               buckets[i].push_back(num);
           }
      -    // 2. 各バケットをソート
      +    // 2. 各バケットをソートする
           for (vector<float> &bucket : buckets) {
      -        // 組み込みソート関数を使用、他のソートアルゴリズムに置き換えることも可能
      +        // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい
               sort(bucket.begin(), bucket.end());
           }
      -    // 3. バケットを走査して結果をマージ
      +    // 3. バケットを走査して結果を結合
           int i = 0;
           for (vector<float> &bucket : buckets) {
               for (float num : bucket) {
      @@ -4447,25 +4447,25 @@
       
      bucket_sort.java
      /* バケットソート */
       void bucketSort(float[] nums) {
      -    // k = n/2 個のバケットを初期化、各バケットに期待される要素数は 2 個
      +    // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする
           int k = nums.length / 2;
           List<List<Float>> buckets = new ArrayList<>();
           for (int i = 0; i < k; i++) {
               buckets.add(new ArrayList<>());
           }
      -    // 1. 配列要素を各バケットに分散
      +    // 1. 配列要素を各バケットに振り分ける
           for (float num : nums) {
      -        // 入力データ範囲は [0, 1)、num * k を使ってインデックス範囲 [0, k-1] にマッピング
      +        // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する
               int i = (int) (num * k);
               // num をバケット i に追加
               buckets.get(i).add(num);
           }
      -    // 2. 各バケットをソート
      +    // 2. 各バケットをソートする
           for (List<Float> bucket : buckets) {
      -        // 組み込みソート関数を使用、他のソートアルゴリズムに置き換えることも可能
      +        // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい
               Collections.sort(bucket);
           }
      -    // 3. バケットを走査して結果をマージ
      +    // 3. バケットを走査して結果を結合
           int i = 0;
           for (List<Float> bucket : buckets) {
               for (float num : bucket) {
      @@ -4476,65 +4476,336 @@
       
      -
      bucket_sort.cs
      [class]{bucket_sort}-[func]{BucketSort}
      +
      bucket_sort.cs
      /* バケットソート */
      +void BucketSort(float[] nums) {
      +    // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする
      +    int k = nums.Length / 2;
      +    List<List<float>> buckets = [];
      +    for (int i = 0; i < k; i++) {
      +        buckets.Add([]);
      +    }
      +    // 1. 配列要素を各バケットに振り分ける
      +    foreach (float num in nums) {
      +        // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する
      +        int i = (int)(num * k);
      +        // num をバケット i に追加
      +        buckets[i].Add(num);
      +    }
      +    // 2. 各バケットをソートする
      +    foreach (List<float> bucket in buckets) {
      +        // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい
      +        bucket.Sort();
      +    }
      +    // 3. バケットを走査して結果を結合
      +    int j = 0;
      +    foreach (List<float> bucket in buckets) {
      +        foreach (float num in bucket) {
      +            nums[j++] = num;
      +        }
      +    }
      +}
       
      -
      bucket_sort.go
      [class]{}-[func]{bucketSort}
      +
      bucket_sort.go
      /* バケットソート */
      +func bucketSort(nums []float64) {
      +    // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする
      +    k := len(nums) / 2
      +    buckets := make([][]float64, k)
      +    for i := 0; i < k; i++ {
      +        buckets[i] = make([]float64, 0)
      +    }
      +    // 1. 配列要素を各バケットに振り分ける
      +    for _, num := range nums {
      +        // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する
      +        i := int(num * float64(k))
      +        // num をバケット i に追加
      +        buckets[i] = append(buckets[i], num)
      +    }
      +    // 2. 各バケットをソートする
      +    for i := 0; i < k; i++ {
      +        // 組み込みのスライスソート関数を使う。ほかのソートアルゴリズムに置き換えてもよい
      +        sort.Float64s(buckets[i])
      +    }
      +    // 3. バケットを走査して結果を結合
      +    i := 0
      +    for _, bucket := range buckets {
      +        for _, num := range bucket {
      +            nums[i] = num
      +            i++
      +        }
      +    }
      +}
       
      -
      bucket_sort.swift
      [class]{}-[func]{bucketSort}
      +
      bucket_sort.swift
      /* バケットソート */
      +func bucketSort(nums: inout [Double]) {
      +    // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする
      +    let k = nums.count / 2
      +    var buckets = (0 ..< k).map { _ in [Double]() }
      +    // 1. 配列要素を各バケットに振り分ける
      +    for num in nums {
      +        // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する
      +        let i = Int(num * Double(k))
      +        // num をバケット i に追加
      +        buckets[i].append(num)
      +    }
      +    // 2. 各バケットをソートする
      +    for i in buckets.indices {
      +        // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい
      +        buckets[i].sort()
      +    }
      +    // 3. バケットを走査して結果を結合
      +    var i = nums.startIndex
      +    for bucket in buckets {
      +        for num in bucket {
      +            nums[i] = num
      +            i += 1
      +        }
      +    }
      +}
       
      -
      bucket_sort.js
      [class]{}-[func]{bucketSort}
      +
      bucket_sort.js
      /* バケットソート */
      +function bucketSort(nums) {
      +    // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする
      +    const k = nums.length / 2;
      +    const buckets = [];
      +    for (let i = 0; i < k; i++) {
      +        buckets.push([]);
      +    }
      +    // 1. 配列要素を各バケットに振り分ける
      +    for (const num of nums) {
      +        // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する
      +        const i = Math.floor(num * k);
      +        // num をバケット i に追加
      +        buckets[i].push(num);
      +    }
      +    // 2. 各バケットをソートする
      +    for (const bucket of buckets) {
      +        // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい
      +        bucket.sort((a, b) => a - b);
      +    }
      +    // 3. バケットを走査して結果を結合
      +    let i = 0;
      +    for (const bucket of buckets) {
      +        for (const num of bucket) {
      +            nums[i++] = num;
      +        }
      +    }
      +}
       
      -
      bucket_sort.ts
      [class]{}-[func]{bucketSort}
      +
      bucket_sort.ts
      /* バケットソート */
      +function bucketSort(nums: number[]): void {
      +    // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする
      +    const k = nums.length / 2;
      +    const buckets: number[][] = [];
      +    for (let i = 0; i < k; i++) {
      +        buckets.push([]);
      +    }
      +    // 1. 配列要素を各バケットに振り分ける
      +    for (const num of nums) {
      +        // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する
      +        const i = Math.floor(num * k);
      +        // num をバケット i に追加
      +        buckets[i].push(num);
      +    }
      +    // 2. 各バケットをソートする
      +    for (const bucket of buckets) {
      +        // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい
      +        bucket.sort((a, b) => a - b);
      +    }
      +    // 3. バケットを走査して結果を結合
      +    let i = 0;
      +    for (const bucket of buckets) {
      +        for (const num of bucket) {
      +            nums[i++] = num;
      +        }
      +    }
      +}
       
      -
      bucket_sort.dart
      [class]{}-[func]{bucketSort}
      +
      bucket_sort.dart
      /* バケットソート */
      +void bucketSort(List<double> nums) {
      +  // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする
      +  int k = nums.length ~/ 2;
      +  List<List<double>> buckets = List.generate(k, (index) => []);
      +
      +  // 1. 配列要素を各バケットに振り分ける
      +  for (double _num in nums) {
      +    // 入力データの範囲は [0, 1) であり、_num * k を用いてインデックス範囲 [0, k-1] に写像する
      +    int i = (_num * k).toInt();
      +    // _num をバケット bucket_idx に追加
      +    buckets[i].add(_num);
      +  }
      +  // 2. 各バケットをソートする
      +  for (List<double> bucket in buckets) {
      +    bucket.sort();
      +  }
      +  // 3. バケットを走査して結果を結合
      +  int i = 0;
      +  for (List<double> bucket in buckets) {
      +    for (double _num in bucket) {
      +      nums[i++] = _num;
      +    }
      +  }
      +}
       
      -
      bucket_sort.rs
      [class]{}-[func]{bucket_sort}
      +
      bucket_sort.rs
      /* バケットソート */
      +fn bucket_sort(nums: &mut [f64]) {
      +    // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする
      +    let k = nums.len() / 2;
      +    let mut buckets = vec![vec![]; k];
      +    // 1. 配列要素を各バケットに振り分ける
      +    for &num in nums.iter() {
      +        // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する
      +        let i = (num * k as f64) as usize;
      +        // num をバケット i に追加
      +        buckets[i].push(num);
      +    }
      +    // 2. 各バケットをソートする
      +    for bucket in &mut buckets {
      +        // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい
      +        bucket.sort_by(|a, b| a.partial_cmp(b).unwrap());
      +    }
      +    // 3. バケットを走査して結果を結合
      +    let mut i = 0;
      +    for bucket in buckets.iter() {
      +        for &num in bucket.iter() {
      +            nums[i] = num;
      +            i += 1;
      +        }
      +    }
      +}
       
      -
      bucket_sort.c
      [class]{}-[func]{bucketSort}
      +
      bucket_sort.c
      /* バケットソート */
      +void bucketSort(float nums[], int n) {
      +    int k = n / 2;                                 // k = n/2 個のバケットを初期化する
      +    int *sizes = malloc(k * sizeof(int));          // 各バケットのサイズを記録する
      +    float **buckets = malloc(k * sizeof(float *)); // 動的配列の配列(バケット)
      +    // 各バケットに十分な容量を事前確保する
      +    for (int i = 0; i < k; ++i) {
      +        buckets[i] = (float *)malloc(n * sizeof(float));
      +        sizes[i] = 0;
      +    }
      +    // 1. 配列要素を各バケットに振り分ける
      +    for (int i = 0; i < n; ++i) {
      +        int idx = (int)(nums[i] * k);
      +        buckets[idx][sizes[idx]++] = nums[i];
      +    }
      +    // 2. 各バケットをソートする
      +    for (int i = 0; i < k; ++i) {
      +        qsort(buckets[i], sizes[i], sizeof(float), compare);
      +    }
      +    // 3. ソート済みのバケットを結合する
      +    int idx = 0;
      +    for (int i = 0; i < k; ++i) {
      +        for (int j = 0; j < sizes[i]; ++j) {
      +            nums[idx++] = buckets[i][j];
      +        }
      +        // メモリを解放する
      +        free(buckets[i]);
      +    }
      +}
       
      -
      bucket_sort.kt
      [class]{}-[func]{bucketSort}
      +
      bucket_sort.kt
      /* バケットソート */
      +fun bucketSort(nums: FloatArray) {
      +    // k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする
      +    val k = nums.size / 2
      +    val buckets = mutableListOf<MutableList<Float>>()
      +    for (i in 0..<k) {
      +        buckets.add(mutableListOf())
      +    }
      +    // 1. 配列要素を各バケットに振り分ける
      +    for (num in nums) {
      +        // 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する
      +        val i = (num * k).toInt()
      +        // num をバケット i に追加
      +        buckets[i].add(num)
      +    }
      +    // 2. 各バケットをソートする
      +    for (bucket in buckets) {
      +        // 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい
      +        bucket.sort()
      +    }
      +    // 3. バケットを走査して結果を結合
      +    var i = 0
      +    for (bucket in buckets) {
      +        for (num in bucket) {
      +            nums[i++] = num
      +        }
      +    }
      +}
       
      -
      bucket_sort.rb
      [class]{}-[func]{bucket_sort}
      +
      bucket_sort.rb
      ### バケットソート ###
      +def bucket_sort(nums)
      +  # k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする
      +  k = nums.length / 2
      +  buckets = Array.new(k) { [] }
      +
      +  # 1. 配列要素を各バケットに振り分ける
      +  nums.each do |num|
      +    # 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する
      +    i = (num * k).to_i
      +    # num をバケット i に追加
      +    buckets[i] << num
      +  end
      +
      +  # 2. 各バケットをソートする
      +  buckets.each do |bucket|
      +    # 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい
      +    bucket.sort!
      +  end
      +
      +  # 3. バケットを走査して結果を結合
      +  i = 0
      +  buckets.each do |bucket|
      +    bucket.each do |num|
      +      nums[i] = num
      +      i += 1
      +    end
      +  end
      +end
       
      -

      11.8.2   アルゴリズムの特徴

      -

      バケットソートは非常に大きなデータセットの処理に適しています。例えば、入力データに100万個の要素が含まれ、システムメモリの制限によりすべてのデータを同時にロードできない場合、データを1,000個のバケットに分割し、各バケットを個別にソートしてから結果をマージできます。

      +
      +コードの可視化 +

      +

      +
      +

      11.8.2   アルゴリズムの特性

      +

      バケットソートは、非常に大規模なデータの処理に適しています。たとえば、入力データに 100 万個の要素が含まれ、空間の制約によりシステムメモリへすべてのデータを一度に読み込めない場合です。このとき、データを 1000 個のバケットに分け、それぞれのバケットを個別にソートしてから、最後に結果を結合できます。

        -
      • 時間計算量は \(O(n + k)\):要素がバケット間で均等に分散されていると仮定すると、各バケット内の要素数は \(n/k\) です。単一のバケットのソートに \(O(n/k \log(n/k))\) 時間がかかると仮定すると、すべてのバケットのソートに \(O(n \log(n/k))\) 時間がかかります。バケット数 \(k\) が比較的大きいとき、時間計算量は \(O(n)\) に近づきます。結果のマージには、すべてのバケットと要素を走査する必要があり、\(O(n + k)\) 時間がかかります。最悪の場合、すべてのデータが単一のバケットに分散され、そのバケットのソートには \(O(n^2)\) 時間がかかります。
      • -
      • 空間計算量は \(O(n + k)\)、非インプレースソート\(k\) 個のバケットと合計 \(n\) 個の要素のための追加スペースが必要です。
      • -
      • バケットソートが安定かどうかは、各バケット内で使用されるソートアルゴリズムが安定かどうかに依存します。
      • +
      • 時間計算量は \(O(n + k)\) :要素が各バケット内に平均的に分布していると仮定すると、各バケット内の要素数は \(\frac{n}{k}\) です。1 つのバケットをソートするのに \(O(\frac{n}{k} \log\frac{n}{k})\) の時間がかかるなら、すべてのバケットのソートには \(O(n \log\frac{n}{k})\) の時間がかかります。バケット数 \(k\) が十分大きいとき、時間計算量は \(O(n)\) に近づきます。結果を結合する際には、すべてのバケットと要素を走査する必要があり、\(O(n + k)\) の時間を要します。最悪の場合、すべてのデータが 1 つのバケットに割り当てられ、そのバケットのソートに \(O(n^2)\) の時間がかかります。
      • +
      • 空間計算量は \(O(n + k)\)、非インプレースソート\(k\) 個のバケットと合計 \(n\) 個の要素ぶんの追加領域が必要です。
      • +
      • バケットソートが安定かどうかは、バケット内要素のソートに用いるアルゴリズムが安定かどうかに依存します。
      -

      11.8.3   均等分散を達成する方法

      -

      バケットソートの理論的時間計算量は \(O(n)\) に達することができます。重要なことは、すべてのバケットに要素を均等に分散させることです。実世界のデータはしばしば均一に分散されていないからです。例えば、eBayのすべての商品を価格範囲で10個のバケットに均等に分散させたいとします。しかし、商品価格の分散は均等でない可能性があり、100ドル未満の商品が多く、500ドル以上の商品が少ないかもしれません。価格範囲を均等に10分割すると、各バケットの商品数の差が大きくなります。

      -

      均等分散を達成するために、最初におおよその境界を設定して、データを3つのバケットに大まかに分割できます。分散が完了した後、より多くのアイテムを持つバケットをさらに3つのバケットに分割し、すべてのバケットの要素数がほぼ等しくなるまで続けます

      -

      以下の図に示すように、この方法は本質的に再帰木を構築し、葉ノードの要素数ができるだけ均等になることを目指します。もちろん、各ラウンドでデータを3つのバケットに分割する必要はありません - 分割戦略はデータの独特な特性に適応的に調整できます。

      -

      バケットの再帰的分割

      -

      図 11-14   バケットの再帰的分割

      +

      11.8.3   均等な分配を実現するには

      +

      バケットソートの時間計算量は理論上 \(O(n)\) に達しますが、鍵は要素を各バケットへ均等に分配すること にあります。実際のデータは均一に分布していないことが多いからです。たとえば、Taobao 上のすべての商品を価格帯ごとに 10 個のバケットへ均等に分けたいとしても、商品の価格分布は偏っており、100 元未満は非常に多く、1000 元超は非常に少ないかもしれません。価格区間を単純に 10 等分すると、各バケットの商品数には大きな差が生じます。

      +

      均等な分配を実現するために、まず大まかな境界線を設定し、データをひとまず 3 個のバケットに粗く振り分けます。分配後は、商品数の多いバケットをさらに 3 個のバケットに分割し、すべてのバケット内の要素数がおおむね等しくなるまでこれを続けます

      +

      以下の図に示すように、この方法の本質は再帰木を構築することにあり、目標は葉ノードの値をできるだけ均等にすることです。もちろん、毎回データを 3 個のバケットに分割する必要はなく、具体的な分け方はデータの特徴に応じて柔軟に選べます。

      +

      再帰的にバケットを分割

      +

      図 11-14   再帰的にバケットを分割

      -

      商品価格の確率分布を事前に知っている場合、データの確率分布に基づいて各バケットの価格境界を設定できます。データ分布を具体的に計算する必要は必ずしもなく、代わりに確率モデルを使用してデータ特性に基づいて近似できることに注意してください。

      -

      以下の図に示すように、商品価格が正規分布に従うと仮定すると、バケット間でアイテムの分散のバランスを取るために合理的な価格区間を定義できます。

      -

      確率分布に基づくバケット分割

      -

      図 11-15   確率分布に基づくバケット分割

      +

      商品価格の確率分布をあらかじめ把握しているなら、データの確率分布に基づいて各バケットの価格境界を設定できます。なお、データ分布は必ずしも特別に統計を取る必要はなく、データの特徴に応じて何らかの確率モデルで近似することもできます。

      +

      以下の図に示すように、商品価格が正規分布に従うと仮定すれば、価格区間を合理的に設定でき、それによって商品を各バケットへ均等に分配できます。

      +

      確率分布に基づいてバケットを分割

      +

      図 11-15   確率分布に基づいてバケットを分割

      diff --git a/ja/chapter_sorting/counting_sort/index.html b/ja/chapter_sorting/counting_sort/index.html index 01f28a24f..b298fb883 100644 --- a/ja/chapter_sorting/counting_sort/index.html +++ b/ja/chapter_sorting/counting_sort/index.html @@ -6,7 +6,7 @@ - + @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1591,7 +1591,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1613,7 +1613,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1635,7 +1635,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1942,7 +1942,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2105,7 +2105,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2491,7 +2491,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2547,7 +2547,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2575,7 +2575,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -3007,7 +3007,7 @@ - 11.9.1   簡単な実装 + 11.9.1   単純な実装 @@ -3029,7 +3029,7 @@ - 11.9.3   アルゴリズムの特徴 + 11.9.3   アルゴリズムの特性 @@ -3040,7 +3040,7 @@ - 11.9.4   制限事項 + 11.9.4   制約 @@ -3207,7 +3207,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3263,7 +3263,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3291,7 +3291,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3484,7 +3484,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3512,7 +3512,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3653,7 +3653,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3681,7 +3681,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3709,7 +3709,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3737,7 +3737,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3930,7 +3930,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4175,7 +4175,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4321,7 +4321,7 @@ - 11.9.1   簡単な実装 + 11.9.1   単純な実装 @@ -4343,7 +4343,7 @@ - 11.9.3   アルゴリズムの特徴 + 11.9.3   アルゴリズムの特性 @@ -4354,7 +4354,7 @@ - 11.9.4   制限事項 + 11.9.4   制約 @@ -4399,57 +4399,55 @@

      11.9   計数ソート

      -

      計数ソートは要素の数をカウントすることでソートを実現し、通常は整数配列に適用されます。

      -

      11.9.1   簡単な実装

      -

      簡単な例から始めましょう。長さ \(n\) の配列 nums が与えられ、すべての要素が「非負整数」である場合、計数ソートの全体的な過程は以下の図に示されています。

      +

      計数ソート(counting sort)は要素数を集計することでソートを実現し、通常は整数配列に適用されます。

      +

      11.9.1   単純な実装

      +

      まず簡単な例を見てみましょう。長さ \(n\) の配列 nums が与えられ、その要素はすべて「非負整数」であるとします。計数ソートの全体的な流れを以下の図に示します。

        -
      1. 配列を走査して最大数を見つけ、それを \(m\) とし、長さ \(m + 1\) の補助配列 counter を作成します。
      2. -
      3. counter を使用して nums 内の各数の出現回数をカウントします。ここで counter[num] は数 num の出現回数に対応します。カウント方法は簡単で、nums を走査し(現在の数を num とする)、各ラウンドで counter[num]\(1\) 増やします。
      4. -
      5. counter のインデックスは自然に順序付けられているため、すべての数は本質的にすでにソートされています。次に、counter を走査し、出現順に nums を昇順で埋めます。
      6. +
      7. 配列を走査し、その中の最大値を見つけて \(m\) とし、続いて長さ \(m + 1\) の補助配列 counter を作成します。
      8. +
      9. counter を用いて nums 内の各数値の出現回数を集計します。ここで counter[num] は数値 num の出現回数に対応します。集計方法は非常に簡単で、nums を走査し(現在の数値を num とする)、各回で counter[num]\(1\) 増やせばよいです。
      10. +
      11. counter の各インデックスは自然に順序づけられているため、すべての数値はすでに整列された状態とみなせます。続いて counter を走査し、各数値の出現回数に応じて小さい順に nums へ書き戻せば完了です。
      -

      計数ソートの過程

      -

      図 11-16   計数ソートの過程

      +

      計数ソートの流れ

      +

      図 11-16   計数ソートの流れ

      -

      コードは以下の通りです:

      +

      コードは以下のとおりです:

      counting_sort.py
      def counting_sort_naive(nums: list[int]):
           """計数ソート"""
      -    # シンプルな実装、オブジェクトのソートには使用できない
      -    # 1. 配列内の最大要素 m を統計
      -    m = 0
      -    for num in nums:
      -        m = max(m, num)
      -    # 2. 各数字の出現回数を統計
      -    # counter[num] は num の出現回数を表す
      -    counter = [0] * (m + 1)
      -    for num in nums:
      -        counter[num] += 1
      -    # 3. counter を走査し、各要素を元の配列 nums に埋め戻し
      -    i = 0
      -    for num in range(m + 1):
      -        for _ in range(counter[num]):
      -            nums[i] = num
      -            i += 1
      +    # 簡易版。オブジェクトのソートには使えない
      +    # 1. 配列の最大要素 m を求める
      +    m = max(nums)
      +    # 2. 各数値の出現回数を数える
      +    # counter[num] は num の出現回数を表す
      +    counter = [0] * (m + 1)
      +    for num in nums:
      +        counter[num] += 1
      +    # 3. counter を走査し、各要素を元の配列 nums に書き戻す
      +    i = 0
      +    for num in range(m + 1):
      +        for _ in range(counter[num]):
      +            nums[i] = num
      +            i += 1
       
      -
      counting_sort.cpp
      /* カウントソート */
      -// 簡単な実装、オブジェクトのソートには使用できない
      +
      counting_sort.cpp
      /* 計数ソート */
      +// 簡易実装のため、オブジェクトのソートには使えない
       void countingSortNaive(vector<int> &nums) {
      -    // 1. 配列の最大要素mを統計
      +    // 1. 配列の最大要素 m を求める
           int m = 0;
           for (int num : nums) {
               m = max(m, num);
           }
      -    // 2. 各数字の出現回数を統計
      -    // counter[num]はnumの出現回数を表す
      +    // 2. 各数値の出現回数を数える
      +    // counter[num] は num の出現回数を表す
           vector<int> counter(m + 1, 0);
           for (int num : nums) {
               counter[num]++;
           }
      -    // 3. counterを走査し、各要素を元の配列numsに戻す
      +    // 3. counter を走査し、各要素を元の配列 nums に書き戻す
           int i = 0;
           for (int num = 0; num < m + 1; num++) {
               for (int j = 0; j < counter[num]; j++, i++) {
      @@ -4461,20 +4459,20 @@
       
      counting_sort.java
      /* 計数ソート */
      -// 簡単な実装、オブジェクトのソートには使用できない
      +// 簡易実装のため、オブジェクトのソートには使えない
       void countingSortNaive(int[] nums) {
      -    // 1. 配列の最大要素 m を統計
      +    // 1. 配列の最大要素 m を求める
           int m = 0;
           for (int num : nums) {
               m = Math.max(m, num);
           }
      -    // 2. 各数字の出現回数を統計
      +    // 2. 各数値の出現回数を数える
           // counter[num] は num の出現回数を表す
           int[] counter = new int[m + 1];
           for (int num : nums) {
               counter[num]++;
           }
      -    // 3. counter を走査し、各要素を元の配列 nums に戻す
      +    // 3. counter を走査し、各要素を元の配列 nums に書き戻す
           int i = 0;
           for (int num = 0; num < m + 1; num++) {
               for (int j = 0; j < counter[num]; j++, i++) {
      @@ -4485,67 +4483,278 @@
       
      -
      counting_sort.cs
      [class]{counting_sort}-[func]{CountingSortNaive}
      +
      counting_sort.cs
      /* 計数ソート */
      +// 簡易実装のため、オブジェクトのソートには使えない
      +void CountingSortNaive(int[] nums) {
      +    // 1. 配列の最大要素 m を求める
      +    int m = 0;
      +    foreach (int num in nums) {
      +        m = Math.Max(m, num);
      +    }
      +    // 2. 各数値の出現回数を数える
      +    // counter[num] は num の出現回数を表す
      +    int[] counter = new int[m + 1];
      +    foreach (int num in nums) {
      +        counter[num]++;
      +    }
      +    // 3. counter を走査し、各要素を元の配列 nums に書き戻す
      +    int i = 0;
      +    for (int num = 0; num < m + 1; num++) {
      +        for (int j = 0; j < counter[num]; j++, i++) {
      +            nums[i] = num;
      +        }
      +    }
      +}
       
      -
      counting_sort.go
      [class]{}-[func]{countingSortNaive}
      +
      counting_sort.go
      /* 計数ソート */
      +// 簡易実装のため、オブジェクトのソートには使えない
      +func countingSortNaive(nums []int) {
      +    // 1. 配列の最大要素 m を求める
      +    m := 0
      +    for _, num := range nums {
      +        if num > m {
      +            m = num
      +        }
      +    }
      +    // 2. 各数値の出現回数を数える
      +    // counter[num] は num の出現回数を表す
      +    counter := make([]int, m+1)
      +    for _, num := range nums {
      +        counter[num]++
      +    }
      +    // 3. counter を走査し、各要素を元の配列 nums に書き戻す
      +    for i, num := 0, 0; num < m+1; num++ {
      +        for j := 0; j < counter[num]; j++ {
      +            nums[i] = num
      +            i++
      +        }
      +    }
      +}
       
      -
      counting_sort.swift
      [class]{}-[func]{countingSortNaive}
      +
      counting_sort.swift
      /* 計数ソート */
      +// 簡易実装のため、オブジェクトのソートには使えない
      +func countingSortNaive(nums: inout [Int]) {
      +    // 1. 配列の最大要素 m を求める
      +    let m = nums.max()!
      +    // 2. 各数値の出現回数を数える
      +    // counter[num] は num の出現回数を表す
      +    var counter = Array(repeating: 0, count: m + 1)
      +    for num in nums {
      +        counter[num] += 1
      +    }
      +    // 3. counter を走査し、各要素を元の配列 nums に書き戻す
      +    var i = 0
      +    for num in 0 ..< m + 1 {
      +        for _ in 0 ..< counter[num] {
      +            nums[i] = num
      +            i += 1
      +        }
      +    }
      +}
       
      -
      counting_sort.js
      [class]{}-[func]{countingSortNaive}
      +
      counting_sort.js
      /* 計数ソート */
      +// 簡易実装のため、オブジェクトのソートには使えない
      +function countingSortNaive(nums) {
      +    // 1. 配列の最大要素 m を求める
      +    let m = Math.max(...nums);
      +    // 2. 各数値の出現回数を数える
      +    // counter[num] は num の出現回数を表す
      +    const counter = new Array(m + 1).fill(0);
      +    for (const num of nums) {
      +        counter[num]++;
      +    }
      +    // 3. counter を走査し、各要素を元の配列 nums に書き戻す
      +    let i = 0;
      +    for (let num = 0; num < m + 1; num++) {
      +        for (let j = 0; j < counter[num]; j++, i++) {
      +            nums[i] = num;
      +        }
      +    }
      +}
       
      -
      counting_sort.ts
      [class]{}-[func]{countingSortNaive}
      +
      counting_sort.ts
      /* 計数ソート */
      +// 簡易実装のため、オブジェクトのソートには使えない
      +function countingSortNaive(nums: number[]): void {
      +    // 1. 配列の最大要素 m を求める
      +    let m: number = Math.max(...nums);
      +    // 2. 各数値の出現回数を数える
      +    // counter[num] は num の出現回数を表す
      +    const counter: number[] = new Array<number>(m + 1).fill(0);
      +    for (const num of nums) {
      +        counter[num]++;
      +    }
      +    // 3. counter を走査し、各要素を元の配列 nums に書き戻す
      +    let i = 0;
      +    for (let num = 0; num < m + 1; num++) {
      +        for (let j = 0; j < counter[num]; j++, i++) {
      +            nums[i] = num;
      +        }
      +    }
      +}
       
      -
      counting_sort.dart
      [class]{}-[func]{countingSortNaive}
      +
      counting_sort.dart
      /* 計数ソート */
      +// 簡易実装のため、オブジェクトのソートには使えない
      +void countingSortNaive(List<int> nums) {
      +  // 1. 配列の最大要素 m を求める
      +  int m = 0;
      +  for (int _num in nums) {
      +    m = max(m, _num);
      +  }
      +  // 2. 各数値の出現回数を数える
      +  // counter[_num] は _num の出現回数を表す
      +  List<int> counter = List.filled(m + 1, 0);
      +  for (int _num in nums) {
      +    counter[_num]++;
      +  }
      +  // 3. counter を走査し、各要素を元の配列 nums に書き戻す
      +  int i = 0;
      +  for (int _num = 0; _num < m + 1; _num++) {
      +    for (int j = 0; j < counter[_num]; j++, i++) {
      +      nums[i] = _num;
      +    }
      +  }
      +}
       
      -
      counting_sort.rs
      [class]{}-[func]{counting_sort_naive}
      +
      counting_sort.rs
      /* 計数ソート */
      +// 簡易実装のため、オブジェクトのソートには使えない
      +fn counting_sort_naive(nums: &mut [i32]) {
      +    // 1. 配列の最大要素 m を求める
      +    let m = *nums.iter().max().unwrap();
      +    // 2. 各数値の出現回数を数える
      +    // counter[num] は num の出現回数を表す
      +    let mut counter = vec![0; m as usize + 1];
      +    for &num in nums.iter() {
      +        counter[num as usize] += 1;
      +    }
      +    // 3. counter を走査し、各要素を元の配列 nums に書き戻す
      +    let mut i = 0;
      +    for num in 0..m + 1 {
      +        for _ in 0..counter[num as usize] {
      +            nums[i] = num;
      +            i += 1;
      +        }
      +    }
      +}
       
      -
      counting_sort.c
      [class]{}-[func]{countingSortNaive}
      +
      counting_sort.c
      /* 計数ソート */
      +// 簡易実装のため、オブジェクトのソートには使えない
      +void countingSortNaive(int nums[], int size) {
      +    // 1. 配列の最大要素 m を求める
      +    int m = 0;
      +    for (int i = 0; i < size; i++) {
      +        if (nums[i] > m) {
      +            m = nums[i];
      +        }
      +    }
      +    // 2. 各数値の出現回数を数える
      +    // counter[num] は num の出現回数を表す
      +    int *counter = calloc(m + 1, sizeof(int));
      +    for (int i = 0; i < size; i++) {
      +        counter[nums[i]]++;
      +    }
      +    // 3. counter を走査し、各要素を元の配列 nums に書き戻す
      +    int i = 0;
      +    for (int num = 0; num < m + 1; num++) {
      +        for (int j = 0; j < counter[num]; j++, i++) {
      +            nums[i] = num;
      +        }
      +    }
      +    // 4. メモリを解放する
      +    free(counter);
      +}
       
      -
      counting_sort.kt
      [class]{}-[func]{countingSortNaive}
      +
      counting_sort.kt
      /* 計数ソート */
      +// 簡易実装のため、オブジェクトのソートには使えない
      +fun countingSortNaive(nums: IntArray) {
      +    // 1. 配列の最大要素 m を求める
      +    var m = 0
      +    for (num in nums) {
      +        m = max(m, num)
      +    }
      +    // 2. 各数値の出現回数を数える
      +    // counter[num] は num の出現回数を表す
      +    val counter = IntArray(m + 1)
      +    for (num in nums) {
      +        counter[num]++
      +    }
      +    // 3. counter を走査し、各要素を元の配列 nums に書き戻す
      +    var i = 0
      +    for (num in 0..<m + 1) {
      +        var j = 0
      +        while (j < counter[num]) {
      +            nums[i] = num
      +            j++
      +            i++
      +        }
      +    }
      +}
       
      -
      counting_sort.rb
      [class]{}-[func]{counting_sort_naive}
      +
      counting_sort.rb
      ### 計数ソート ###
      +def counting_sort_naive(nums)
      +  # 簡易版。オブジェクトのソートには使えない
      +  # 1. 配列の最大要素 m を求める
      +  m = 0
      +  nums.each { |num| m = [m, num].max }
      +  # 2. 各数値の出現回数を数える
      +  # counter[num] は num の出現回数を表す
      +  counter = Array.new(m + 1, 0)
      +  nums.each { |num| counter[num] += 1 }
      +  # 3. counter を走査し、各要素を元の配列 nums に書き戻す
      +  i = 0
      +  for num in 0...(m + 1)
      +    (0...counter[num]).each do
      +      nums[i] = num
      +      i += 1
      +    end
      +  end
      +end
       
      +
      +コードの可視化 +

      +

      +

      計数ソートとバケットソートの関係

      -

      バケットソートの観点から、計数ソートにおける計数配列 counter の各インデックスをバケットと考え、カウントの過程を要素を対応するバケットに分散させることと考えることができます。本質的に、計数ソートは整数データのためのバケットソートの特別なケースです。

      +

      バケットソートの観点から見ると、計数ソートにおける計数配列 counter の各インデックスを 1 つのバケットとみなし、個数を数える過程を各要素を対応するバケットへ振り分ける操作とみなせます。本質的には、計数ソートは整数データにおけるバケットソートの特殊な一例です。

      11.9.2   完全な実装

      -

      注意深い読者は気付くかもしれませんが、入力データがオブジェクトの場合、上記の手順 3. は無効です。入力データが商品オブジェクトで、価格(クラスメンバ変数)で商品をソートしたいとします。しかし、上記のアルゴリズムは結果としてソート済みの価格のみを提供できます。

      -

      では、元のデータのソート結果をどのように取得できるでしょうか?まず、counter の「前置和」を計算します。名前が示すように、インデックス i での前置和 prefix[i] は、配列の最初の i 個の要素の和に等しいです:

      +

      注意深い読者なら、**入力データがオブジェクトである場合、上記の手順 3. は機能しない**ことに気づくかもしれません。入力データが商品オブジェクトであり、商品価格(クラスのメンバ変数)に基づいて商品をソートしたいとします。しかし上記のアルゴリズムが返せるのは価格のソート結果だけです。

      +

      では、元のデータのソート結果を得るにはどうすればよいのでしょうか。まず counter の「累積和」を計算します。名前のとおり、インデックス i における累積和 prefix[i] は、配列の先頭から i 番目までの要素の総和に等しくなります:

      \[ \text{prefix}[i] = \sum_{j=0}^i \text{counter[j]} \]
      -

      前置和には明確な意味があります。prefix[num] - 1 は結果配列 res における要素 num の最後の出現のインデックスを表します。この情報は重要で、各要素が結果配列のどこに現れるべきかを教えてくれます。次に、元の配列 nums の各要素 num を逆順で走査し、各反復で以下の2つの手順を実行します。

      +

      累積和には明確な意味があり、prefix[num] - 1 は要素 num が結果配列 res に最後に現れるインデックスを表します。この情報は非常に重要で、各要素が結果配列のどの位置に現れるべきかを示してくれます。続いて元の配列 nums を逆順に走査し、各要素 num に対して各反復で次の 2 つの手順を行います。

        -
      1. インデックス prefix[num] - 1 で配列 resnum を埋めます。
      2. -
      3. 前置和 prefix[num]\(1\) 減らして、num を配置する次のインデックスを取得します。
      4. +
      5. num を配列 res のインデックス prefix[num] - 1 に格納します。
      6. +
      7. 累積和 prefix[num]\(1\) 減らし、次に num を配置するインデックスを得ます。
      -

      走査後、配列 res にはソートされた結果が含まれ、最後に res が元の配列 nums を置き換えます。完全な計数ソートの過程は以下の図に示されています。

      +

      走査が完了すると、配列 res にソート済みの結果が格納されます。最後に res で元の配列 nums を上書きすれば完了です。以下の図は完全な計数ソートの流れを示しています。

      -

      計数ソートの過程

      +

      計数ソートの手順

      counting_sort_step2

      @@ -4570,103 +4779,103 @@
      -

      図 11-17   計数ソートの過程

      +

      図 11-17   計数ソートの手順

      -

      計数ソートの実装コードは以下の通りです:

      +

      計数ソートの実装コードは以下のとおりです:

      counting_sort.py
      def counting_sort(nums: list[int]):
           """計数ソート"""
      -    # 完全な実装、オブジェクトのソートが可能で、安定ソート
      -    # 1. 配列内の最大要素 m を統計
      +    # 完全版。オブジェクトをソートでき、かつ安定ソートである
      +    # 1. 配列の最大要素 m を求める
           m = max(nums)
      -    # 2. 各数字の出現回数を統計
      +    # 2. 各数値の出現回数を数える
           # counter[num] は num の出現回数を表す
           counter = [0] * (m + 1)
           for num in nums:
               counter[num] += 1
      -    # 3. counter の前置和を計算し、「出現回数」を「末尾インデックス」に変換
      -    # counter[num]-1 は res において num が最後に出現するインデックス
      +    # 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する
      +    # つまり counter[num]-1 は、num が res に最後に現れるインデックス
           for i in range(m):
               counter[i + 1] += counter[i]
      -    # 4. nums を逆順に走査し、各要素を結果配列 res に配置
      +    # 4. nums を逆順に走査し、各要素を結果配列 res に格納する
           # 結果を記録するための配列 res を初期化
           n = len(nums)
           res = [0] * n
           for i in range(n - 1, -1, -1):
               num = nums[i]
               res[counter[num] - 1] = num  # num を対応するインデックスに配置
      -        counter[num] -= 1  # 前置和を1減らし、num を配置する次のインデックスを取得
      -    # 結果配列 res を使用して元の配列 nums を上書き
      +        counter[num] -= 1  # 累積和を 1 減らして、次に num を配置するインデックスを得る
      +    # 結果配列 res で元の配列 nums を上書きする
           for i in range(n):
               nums[i] = res[i]
       
      -
      counting_sort.cpp
      /* カウントソート */
      -// 完全な実装、オブジェクトのソートが可能で安定ソート
      +
      counting_sort.cpp
      /* 計数ソート */
      +// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
       void countingSort(vector<int> &nums) {
      -    // 1. 配列の最大要素mを統計
      +    // 1. 配列の最大要素 m を求める
           int m = 0;
           for (int num : nums) {
               m = max(m, num);
           }
      -    // 2. 各数字の出現回数を統計
      -    // counter[num]はnumの出現回数を表す
      +    // 2. 各数値の出現回数を数える
      +    // counter[num] は num の出現回数を表す
           vector<int> counter(m + 1, 0);
           for (int num : nums) {
               counter[num]++;
           }
      -    // 3. counterの前缀和を計算し、「出現回数」を「末尾インデックス」に変換
      -    // counter[num]-1はnumがresで現れる最後のインデックス
      +    // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する
      +    // つまり counter[num]-1 は、num が res に最後に現れるインデックス
           for (int i = 0; i < m; i++) {
               counter[i + 1] += counter[i];
           }
      -    // 4. numsを逆順で走査し、各要素を結果配列resに配置
      -    // 結果を記録する配列resを初期化
      +    // 4. nums を逆順に走査し、各要素を結果配列 res に格納する
      +    // 結果を記録するための配列 res を初期化
           int n = nums.size();
           vector<int> res(n);
           for (int i = n - 1; i >= 0; i--) {
               int num = nums[i];
      -        res[counter[num] - 1] = num; // numを対応するインデックスに配置
      -        counter[num]--;              // 前缀和を1減らし、numを配置する次のインデックスを取得
      +        res[counter[num] - 1] = num; // num を対応するインデックスに配置
      +        counter[num]--;              // 累積和を 1 減らして、次に num を配置するインデックスを得る
           }
      -    // 結果配列resで元の配列numsを上書き
      +    // 結果配列 res で元の配列 nums を上書きする
           nums = res;
       }
       
      counting_sort.java
      /* 計数ソート */
      -// 完全な実装、オブジェクトをソートでき、安定ソート
      +// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
       void countingSort(int[] nums) {
      -    // 1. 配列の最大要素 m を統計
      +    // 1. 配列の最大要素 m を求める
           int m = 0;
           for (int num : nums) {
               m = Math.max(m, num);
           }
      -    // 2. 各数字の出現回数を統計
      +    // 2. 各数値の出現回数を数える
           // counter[num] は num の出現回数を表す
           int[] counter = new int[m + 1];
           for (int num : nums) {
               counter[num]++;
           }
      -    // 3. counter の累積和を計算し、「出現回数」を「尻尾インデックス」に変換
      -    // counter[num]-1 は res 内で num が出現する最後のインデックス
      +    // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する
      +    // つまり counter[num]-1 は、num が res に最後に現れるインデックス
           for (int i = 0; i < m; i++) {
               counter[i + 1] += counter[i];
           }
      -    // 4. nums を逆順に走査し、各要素を結果配列 res に配置
      -    // 結果を記録する配列 res を初期化
      +    // 4. nums を逆順に走査し、各要素を結果配列 res に格納する
      +    // 結果を記録するための配列 res を初期化
           int n = nums.length;
           int[] res = new int[n];
           for (int i = n - 1; i >= 0; i--) {
               int num = nums[i];
               res[counter[num] - 1] = num; // num を対応するインデックスに配置
      -        counter[num]--; // 累積和を 1 減算し、num を配置する次のインデックスを取得
      +        counter[num]--; // 累積和を 1 減らして、次に num を配置するインデックスを得る
           }
      -    // 結果配列 res を使って元の配列 nums を上書き
      +    // 結果配列 res で元の配列 nums を上書きする
           for (int i = 0; i < n; i++) {
               nums[i] = res[i];
           }
      @@ -4674,57 +4883,360 @@
       
      -
      counting_sort.cs
      [class]{counting_sort}-[func]{CountingSort}
      +
      counting_sort.cs
      /* 計数ソート */
      +// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
      +void CountingSort(int[] nums) {
      +    // 1. 配列の最大要素 m を求める
      +    int m = 0;
      +    foreach (int num in nums) {
      +        m = Math.Max(m, num);
      +    }
      +    // 2. 各数値の出現回数を数える
      +    // counter[num] は num の出現回数を表す
      +    int[] counter = new int[m + 1];
      +    foreach (int num in nums) {
      +        counter[num]++;
      +    }
      +    // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する
      +    // つまり counter[num]-1 は、num が res に最後に現れるインデックス
      +    for (int i = 0; i < m; i++) {
      +        counter[i + 1] += counter[i];
      +    }
      +    // 4. nums を逆順に走査し、各要素を結果配列 res に格納する
      +    // 結果を記録するための配列 res を初期化
      +    int n = nums.Length;
      +    int[] res = new int[n];
      +    for (int i = n - 1; i >= 0; i--) {
      +        int num = nums[i];
      +        res[counter[num] - 1] = num; // num を対応するインデックスに配置
      +        counter[num]--; // 累積和を 1 減らして、次に num を配置するインデックスを得る
      +    }
      +    // 結果配列 res で元の配列 nums を上書きする
      +    for (int i = 0; i < n; i++) {
      +        nums[i] = res[i];
      +    }
      +}
       
      -
      counting_sort.go
      [class]{}-[func]{countingSort}
      +
      counting_sort.go
      /* 計数ソート */
      +// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
      +func countingSort(nums []int) {
      +    // 1. 配列の最大要素 m を求める
      +    m := 0
      +    for _, num := range nums {
      +        if num > m {
      +            m = num
      +        }
      +    }
      +    // 2. 各数値の出現回数を数える
      +    // counter[num] は num の出現回数を表す
      +    counter := make([]int, m+1)
      +    for _, num := range nums {
      +        counter[num]++
      +    }
      +    // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する
      +    // つまり counter[num]-1 は、num が res に最後に現れるインデックス
      +    for i := 0; i < m; i++ {
      +        counter[i+1] += counter[i]
      +    }
      +    // 4. nums を逆順に走査し、各要素を結果配列 res に格納する
      +    // 結果を記録するための配列 res を初期化
      +    n := len(nums)
      +    res := make([]int, n)
      +    for i := n - 1; i >= 0; i-- {
      +        num := nums[i]
      +        // num を対応するインデックスに配置
      +        res[counter[num]-1] = num
      +        // 累積和を 1 減らして、次に num を配置するインデックスを得る
      +        counter[num]--
      +    }
      +    // 結果配列 res で元の配列 nums を上書きする
      +    copy(nums, res)
      +}
       
      -
      counting_sort.swift
      [class]{}-[func]{countingSort}
      +
      counting_sort.swift
      /* 計数ソート */
      +// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
      +func countingSort(nums: inout [Int]) {
      +    // 1. 配列の最大要素 m を求める
      +    let m = nums.max()!
      +    // 2. 各数値の出現回数を数える
      +    // counter[num] は num の出現回数を表す
      +    var counter = Array(repeating: 0, count: m + 1)
      +    for num in nums {
      +        counter[num] += 1
      +    }
      +    // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する
      +    // つまり counter[num]-1 は、num が res に最後に現れるインデックス
      +    for i in 0 ..< m {
      +        counter[i + 1] += counter[i]
      +    }
      +    // 4. nums を逆順に走査し、各要素を結果配列 res に格納する
      +    // 結果を記録するための配列 res を初期化
      +    var res = Array(repeating: 0, count: nums.count)
      +    for i in nums.indices.reversed() {
      +        let num = nums[i]
      +        res[counter[num] - 1] = num // num を対応するインデックスに配置
      +        counter[num] -= 1 // 累積和を 1 減らして、次に num を配置するインデックスを得る
      +    }
      +    // 結果配列 res で元の配列 nums を上書きする
      +    for i in nums.indices {
      +        nums[i] = res[i]
      +    }
      +}
       
      -
      counting_sort.js
      [class]{}-[func]{countingSort}
      +
      counting_sort.js
      /* 計数ソート */
      +// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
      +function countingSort(nums) {
      +    // 1. 配列の最大要素 m を求める
      +    let m = Math.max(...nums);
      +    // 2. 各数値の出現回数を数える
      +    // counter[num] は num の出現回数を表す
      +    const counter = new Array(m + 1).fill(0);
      +    for (const num of nums) {
      +        counter[num]++;
      +    }
      +    // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する
      +    // つまり counter[num]-1 は、num が res に最後に現れるインデックス
      +    for (let i = 0; i < m; i++) {
      +        counter[i + 1] += counter[i];
      +    }
      +    // 4. nums を逆順に走査し、各要素を結果配列 res に格納する
      +    // 結果を記録するための配列 res を初期化
      +    const n = nums.length;
      +    const res = new Array(n);
      +    for (let i = n - 1; i >= 0; i--) {
      +        const num = nums[i];
      +        res[counter[num] - 1] = num; // num を対応するインデックスに配置
      +        counter[num]--; // 累積和を 1 減らして、次に num を配置するインデックスを得る
      +    }
      +    // 結果配列 res で元の配列 nums を上書きする
      +    for (let i = 0; i < n; i++) {
      +        nums[i] = res[i];
      +    }
      +}
       
      -
      counting_sort.ts
      [class]{}-[func]{countingSort}
      +
      counting_sort.ts
      /* 計数ソート */
      +// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
      +function countingSort(nums: number[]): void {
      +    // 1. 配列の最大要素 m を求める
      +    let m: number = Math.max(...nums);
      +    // 2. 各数値の出現回数を数える
      +    // counter[num] は num の出現回数を表す
      +    const counter: number[] = new Array<number>(m + 1).fill(0);
      +    for (const num of nums) {
      +        counter[num]++;
      +    }
      +    // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する
      +    // つまり counter[num]-1 は、num が res に最後に現れるインデックス
      +    for (let i = 0; i < m; i++) {
      +        counter[i + 1] += counter[i];
      +    }
      +    // 4. nums を逆順に走査し、各要素を結果配列 res に格納する
      +    // 結果を記録するための配列 res を初期化
      +    const n = nums.length;
      +    const res: number[] = new Array<number>(n);
      +    for (let i = n - 1; i >= 0; i--) {
      +        const num = nums[i];
      +        res[counter[num] - 1] = num; // num を対応するインデックスに配置
      +        counter[num]--; // 累積和を 1 減らして、次に num を配置するインデックスを得る
      +    }
      +    // 結果配列 res で元の配列 nums を上書きする
      +    for (let i = 0; i < n; i++) {
      +        nums[i] = res[i];
      +    }
      +}
       
      -
      counting_sort.dart
      [class]{}-[func]{countingSort}
      +
      counting_sort.dart
      /* 計数ソート */
      +// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
      +void countingSort(List<int> nums) {
      +  // 1. 配列の最大要素 m を求める
      +  int m = 0;
      +  for (int _num in nums) {
      +    m = max(m, _num);
      +  }
      +  // 2. 各数値の出現回数を数える
      +  // counter[_num] は _num の出現回数を表す
      +  List<int> counter = List.filled(m + 1, 0);
      +  for (int _num in nums) {
      +    counter[_num]++;
      +  }
      +  // 3. counter の累積和を求め、「出現回数」を「末尾インデックス」に変換する
      +  // つまり counter[_num]-1 は、res において _num が最後に出現する位置のインデックスである
      +  for (int i = 0; i < m; i++) {
      +    counter[i + 1] += counter[i];
      +  }
      +  // 4. nums を逆順に走査し、各要素を結果配列 res に格納する
      +  // 結果を記録するための配列 res を初期化
      +  int n = nums.length;
      +  List<int> res = List.filled(n, 0);
      +  for (int i = n - 1; i >= 0; i--) {
      +    int _num = nums[i];
      +    res[counter[_num] - 1] = _num; // _num を対応する添字に配置
      +    counter[_num]--; // 累積和を 1 減らし、次に _num を配置するインデックスを得る
      +  }
      +  // 結果配列 res で元の配列 nums を上書きする
      +  nums.setAll(0, res);
      +}
       
      -
      counting_sort.rs
      [class]{}-[func]{counting_sort}
      +
      counting_sort.rs
      /* 計数ソート */
      +// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
      +fn counting_sort(nums: &mut [i32]) {
      +    // 1. 配列の最大要素 m を求める
      +    let m = *nums.iter().max().unwrap() as usize;
      +    // 2. 各数値の出現回数を数える
      +    // counter[num] は num の出現回数を表す
      +    let mut counter = vec![0; m + 1];
      +    for &num in nums.iter() {
      +        counter[num as usize] += 1;
      +    }
      +    // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する
      +    // つまり counter[num]-1 は、num が res に最後に現れるインデックス
      +    for i in 0..m {
      +        counter[i + 1] += counter[i];
      +    }
      +    // 4. nums を逆順に走査し、各要素を結果配列 res に格納する
      +    // 結果を記録するための配列 res を初期化
      +    let n = nums.len();
      +    let mut res = vec![0; n];
      +    for i in (0..n).rev() {
      +        let num = nums[i];
      +        res[counter[num as usize] - 1] = num; // num を対応するインデックスに配置
      +        counter[num as usize] -= 1; // 累積和を 1 減らして、次に num を配置するインデックスを得る
      +    }
      +    // 結果配列 res で元の配列 nums を上書きする
      +    nums.copy_from_slice(&res)
      +}
       
      -
      counting_sort.c
      [class]{}-[func]{countingSort}
      +
      counting_sort.c
      /* 計数ソート */
      +// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
      +void countingSort(int nums[], int size) {
      +    // 1. 配列の最大要素 m を求める
      +    int m = 0;
      +    for (int i = 0; i < size; i++) {
      +        if (nums[i] > m) {
      +            m = nums[i];
      +        }
      +    }
      +    // 2. 各数値の出現回数を数える
      +    // counter[num] は num の出現回数を表す
      +    int *counter = calloc(m, sizeof(int));
      +    for (int i = 0; i < size; i++) {
      +        counter[nums[i]]++;
      +    }
      +    // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する
      +    // つまり counter[num]-1 は、num が res に最後に現れるインデックス
      +    for (int i = 0; i < m; i++) {
      +        counter[i + 1] += counter[i];
      +    }
      +    // 4. nums を逆順に走査し、各要素を結果配列 res に格納する
      +    // 結果を記録するための配列 res を初期化
      +    int *res = malloc(sizeof(int) * size);
      +    for (int i = size - 1; i >= 0; i--) {
      +        int num = nums[i];
      +        res[counter[num] - 1] = num; // num を対応するインデックスに配置
      +        counter[num]--;              // 累積和を 1 減らして、次に num を配置するインデックスを得る
      +    }
      +    // 結果配列 res で元の配列 nums を上書きする
      +    memcpy(nums, res, size * sizeof(int));
      +    // 5. メモリを解放する
      +    free(res);
      +    free(counter);
      +}
       
      -
      counting_sort.kt
      [class]{}-[func]{countingSort}
      +
      counting_sort.kt
      /* 計数ソート */
      +// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
      +fun countingSort(nums: IntArray) {
      +    // 1. 配列の最大要素 m を求める
      +    var m = 0
      +    for (num in nums) {
      +        m = max(m, num)
      +    }
      +    // 2. 各数値の出現回数を数える
      +    // counter[num] は num の出現回数を表す
      +    val counter = IntArray(m + 1)
      +    for (num in nums) {
      +        counter[num]++
      +    }
      +    // 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する
      +    // つまり counter[num]-1 は、num が res に最後に現れるインデックス
      +    for (i in 0..<m) {
      +        counter[i + 1] += counter[i]
      +    }
      +    // 4. nums を逆順に走査し、各要素を結果配列 res に格納する
      +    // 結果を記録するための配列 res を初期化
      +    val n = nums.size
      +    val res = IntArray(n)
      +    for (i in n - 1 downTo 0) {
      +        val num = nums[i]
      +        res[counter[num] - 1] = num // num を対応するインデックスに配置
      +        counter[num]-- // 累積和を 1 減らして、次に num を配置するインデックスを得る
      +    }
      +    // 結果配列 res で元の配列 nums を上書きする
      +    for (i in 0..<n) {
      +        nums[i] = res[i]
      +    }
      +}
       
      -
      counting_sort.rb
      [class]{}-[func]{counting_sort}
      +
      counting_sort.rb
      ### 計数ソート ###
      +def counting_sort(nums)
      +  # 完全版。オブジェクトをソートでき、かつ安定ソートである
      +  # 1. 配列の最大要素 m を求める
      +  m = nums.max
      +  # 2. 各数値の出現回数を数える
      +  # counter[num] は num の出現回数を表す
      +  counter = Array.new(m + 1, 0)
      +  nums.each { |num| counter[num] += 1 }
      +  # 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する
      +  # つまり counter[num]-1 は、num が res に最後に現れるインデックス
      +  (0...m).each { |i| counter[i + 1] += counter[i] }
      +  # 4. nums を逆順に走査し、各要素を結果配列 res に格納する
      +  # 結果を記録するための配列 res を初期化する
      +  n = nums.length
      +  res = Array.new(n, 0)
      +  (n - 1).downto(0).each do |i|
      +    num = nums[i]
      +    res[counter[num] - 1] = num # num を対応するインデックスに配置
      +    counter[num] -= 1 # 累積和を 1 減らして、次に num を配置するインデックスを得る
      +  end
      +  # 結果配列 res で元の配列 nums を上書きする
      +  (0...n).each { |i| nums[i] = res[i] }
      +end
       
      -

      11.9.3   アルゴリズムの特徴

      +
      +コードの可視化 +

      +

      +
      +

      11.9.3   アルゴリズムの特性

        -
      • 時間計算量は \(O(n + m)\)、非適応ソートnumscounter の走査が含まれ、どちらも線形時間を使用します。一般的に、\(n \gg m\) であり、時間計算量は \(O(n)\) に近づきます。
      • -
      • 空間計算量は \(O(n + m)\)、非インプレースソート:長さ \(n\) の配列 res と長さ \(m\) の配列 counter をそれぞれ使用します。
      • -
      • 安定ソート:要素が「右から左」の順序で res に埋められるため、nums の走査を逆順にすることで、等しい要素間の相対位置の変化を防ぎ、安定したソートを実現できます。実際、nums を順番に走査しても正しいソート結果を生成できますが、結果は不安定です。
      • +
      • 時間計算量は \(O(n + m)\)、非適応ソートnums の走査と counter の走査が含まれ、いずれも線形時間です。一般には \(n \gg m\) であり、時間計算量は \(O(n)\) に近づきます。
      • +
      • 空間計算量は \(O(n + m)\)、非インプレースソート:長さがそれぞれ \(n\)\(m\) の配列 rescounter を利用します。
      • +
      • 安定ソートres に要素を埋める順序が「右から左」であるため、nums を逆順に走査することで等しい要素どうしの相対位置が変化するのを防ぎ、安定ソートを実現できます。実際には、nums を順方向に走査しても正しいソート結果は得られますが、その結果は安定ではありません。
      -

      11.9.4   制限事項

      -

      今までに、計数ソートは非常に巧妙だと感じるかもしれません。単に量をカウントするだけで効率的なソートを実現できるからです。しかし、計数ソートを使用するための前提条件は比較的厳しいです。

      -

      計数ソートは非負整数にのみ適用できます。他のタイプのデータに適用したい場合、これらのデータが要素の元の順序を変更することなく非負整数に変換できることを保証する必要があります。例えば、負の整数を含む配列の場合、最初にすべての数に定数を加えて、すべてを正の数に変換し、ソート完了後に元に戻すことができます。

      -

      計数ソートは値の範囲が小さい大きなデータセットに適しています。例えば、上記の例では、\(m\) は大きすぎるべきではありません。そうでなければ、あまりにも多くのスペースを占有してしまいます。そして \(n \ll m\) の場合、計数ソートは \(O(m)\) 時間を使用し、\(O(n \log n)\) ソートアルゴリズムより遅い可能性があります。

      +

      11.9.4   制約

      +

      ここまで読むと、計数ソートは非常に巧妙で、個数を数えるだけで効率的なソートを実現できると感じるかもしれません。しかし、計数ソートを利用するための前提条件は比較的厳格です。

      +

      計数ソートは非負整数にしか適用できません。ほかの型のデータに適用したい場合は、それらのデータを非負整数に変換でき、かつ変換の過程で要素間の相対的な大小関係が変わらないことを保証する必要があります。たとえば、負数を含む整数配列に対しては、すべての数値に定数を加えて正数へ変換し、ソート後に元へ戻すことができます。

      +

      計数ソートはデータ量が多く、値域が小さい場合に適しています。たとえば上記の例では \(m\) が大きすぎてはならず、そうでないと過剰な空間を消費します。また、\(n \ll m\) のとき、計数ソートは \(O(m)\) 時間を要するため、\(O(n \log n)\) のソートアルゴリズムより遅くなる可能性があります。

      diff --git a/ja/chapter_sorting/heap_sort/index.html b/ja/chapter_sorting/heap_sort/index.html index ee143032d..7fade06ba 100644 --- a/ja/chapter_sorting/heap_sort/index.html +++ b/ja/chapter_sorting/heap_sort/index.html @@ -6,7 +6,7 @@ - + @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1591,7 +1591,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1613,7 +1613,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1635,7 +1635,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1942,7 +1942,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2105,7 +2105,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2491,7 +2491,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2547,7 +2547,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2575,7 +2575,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -2962,7 +2962,7 @@ - 11.7.2   アルゴリズムの特徴 + 11.7.2   アルゴリズムの特性 @@ -3185,7 +3185,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3241,7 +3241,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3269,7 +3269,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3462,7 +3462,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3490,7 +3490,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3631,7 +3631,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3659,7 +3659,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3687,7 +3687,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3715,7 +3715,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3908,7 +3908,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4153,7 +4153,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4310,7 +4310,7 @@ - 11.7.2   アルゴリズムの特徴 + 11.7.2   アルゴリズムの特性 @@ -4357,30 +4357,30 @@

      11.7   ヒープソート

      Tip

      -

      この節を読む前に、「ヒープ」の章を必ず完了させてください。

      +

      本節を読む前に、「ヒープ」の章を学習済みであることを確認してください。

      -

      ヒープソートは、ヒープデータ構造に基づく効率的なソートアルゴリズムです。すでに学習した「ヒープの構築」と「要素の抽出」操作を使用してヒープソートを実装できます。

      +

      ヒープソート(heap sort)は、ヒープデータ構造に基づいて実装される効率的なソートアルゴリズムです。すでに学んだ「ヒープ構築操作」と「要素の取り出し操作」を利用してヒープソートを実現できます。

        -
      1. 配列を入力し、最小ヒープを構築します。ここで、最小要素がヒープの頂上に位置します。
      2. -
      3. 継続的に抽出操作を実行し、抽出された要素を順次記録して、最小から最大までのソート済みリストを取得します。
      4. +
      5. 配列を入力して最小ヒープを構築すると、このとき最小要素はヒープの頂点にあります。
      6. +
      7. 取り出し操作を繰り返し実行し、取り出された要素を順に記録すれば、昇順に並んだ列が得られます。
      -

      上記の方法は実現可能ですが、ポップされた要素を格納するための追加の配列が必要で、やや空間を消費します。実際には、通常、より優雅な実装を使用します。

      +

      以上の方法でも実行できますが、取り出した要素を保存するために追加の配列が必要となり、空間をやや無駄にします。実際には、通常はより洗練された実装方法を用います。

      11.7.1   アルゴリズムの流れ

      -

      配列の長さを \(n\) とすると、ヒープソートの過程は以下の通りです。

      +

      配列の長さを \(n\) とすると、ヒープソートの流れは次図のとおりです。

        -
      1. 配列を入力し、最大ヒープを構築します。この手順の後、最大要素がヒープの頂上に位置します。
      2. -
      3. ヒープの頂上要素(最初の要素)とヒープの底部要素(最後の要素)を交換します。この交換の後、ヒープの長さを \(1\) 減らし、ソート済み要素の数を \(1\) 増やします。
      4. -
      5. ヒープの頂上から開始して、上から下へのsift-down操作を実行します。sift-downの後、ヒープの性質が復元されます。
      6. -
      7. 手順 2.3. を繰り返します。\(n - 1\) ラウンドループして、配列のソートを完了します。
      8. +
      9. 配列を入力して最大ヒープを構築します。完了後、最大要素はヒープの頂点にあります。
      10. +
      11. ヒープ頂点の要素(最初の要素)とヒープ末尾の要素(最後の要素)を交換します。交換後、ヒープの長さは \(1\) 減少し、整列済み要素数は \(1\) 増加します。
      12. +
      13. ヒープ頂点の要素から始めて、上から下へヒープ化操作(sift down)を実行します。ヒープ化が完了すると、ヒープの性質が回復します。
      14. +
      15. 2. ステップと第 3. ステップを繰り返し実行します。これを \(n - 1\) 回繰り返すと、配列の整列が完了します。

      Tip

      -

      実際、要素抽出操作も手順 2.3. を含み、抽出された要素をヒープから削除する追加の手順があります。

      +

      実際には、要素の取り出し操作にも第 2. ステップと第 3. ステップが含まれており、要素を取り出す処理が 1 つ加わるだけです。

      -

      ヒープソートの過程

      +

      ヒープソートの手順

      heap_sort_step2

      @@ -4417,16 +4417,16 @@
      -

      図 11-12   ヒープソートの過程

      +

      図 11-12   ヒープソートの手順

      -

      コードの実装では、「ヒープ」の章からのsift-down関数 sift_down() を使用しました。最大要素が抽出されるにつれてヒープの長さが減少するため、sift_down() 関数に長さパラメータ \(n\) を追加して、ヒープの現在の有効長を指定する必要があることに注意することが重要です。コードは以下の通りです:

      +

      コード実装では、「ヒープ」の章と同じ上から下へのヒープ化 sift_down() 関数を使用します。注意すべき点として、ヒープの長さは最大要素を取り出すたびに短くなるため、sift_down() 関数に長さパラメータ \(n\) を追加し、ヒープの現在の有効な長さを指定する必要があります。コードは以下のとおりです。

      heap_sort.py
      def sift_down(nums: list[int], n: int, i: int):
      -    """ヒープの長さが n、ノード i から上から下へヒープ化を開始"""
      +    """ヒープの長さは n。ノード i から下方向にヒープ化"""
           while True:
      -        # i、l、r の中で最大のノードを判定し、ma とする
      +        # ノード i, l, r のうち値が最大のノードを ma とする
               l = 2 * i + 1
               r = 2 * i + 2
               ma = i
      @@ -4434,32 +4434,32 @@
                   ma = l
               if r < n and nums[r] > nums[ma]:
                   ma = r
      -        # ノード i が最大または l、r のインデックスが範囲外の場合、さらなるヒープ化は不要、ループを抜ける
      +        # ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける
               if ma == i:
                   break
      -        # 2つのノードを交換
      +        # 2 つのノードを交換
               nums[i], nums[ma] = nums[ma], nums[i]
      -        # 下向きにヒープ化をループ
      +        # ループで上から下へヒープ化
               i = ma
       
       def heap_sort(nums: list[int]):
           """ヒープソート"""
      -    # ヒープ構築操作:葉ノード以外のすべてのノードをヒープ化
      +    # ヒープ構築:葉ノード以外のすべてのノードをヒープ化する
           for i in range(len(nums) // 2 - 1, -1, -1):
               sift_down(nums, len(nums), i)
      -    # ヒープから最大要素を抽出し、n-1 回繰り返す
      +    # ヒープから最大要素を取り出し、n-1 回繰り返す
           for i in range(len(nums) - 1, 0, -1):
      -        # ルートノードと最も右の葉ノードを交換(最初の要素と最後の要素を交換)
      +        # 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換)
               nums[0], nums[i] = nums[i], nums[0]
      -        # ルートノードから上から下へヒープ化を開始
      +        # 根ノードを起点に、上から下へヒープ化
               sift_down(nums, i, 0)
       
      -
      heap_sort.cpp
      /* ヒープの長さはn、ノードiから上から下へヒープ化を開始 */
      +
      heap_sort.cpp
      /* ヒープの長さは n。ノード i から下方向にヒープ化 */
       void siftDown(vector<int> &nums, int n, int i) {
           while (true) {
      -        // i、l、r の中で最大のノードを決定し、maとして記録
      +        // ノード i, l, r のうち値が最大のノードを ma とする
               int l = 2 * i + 1;
               int r = 2 * i + 2;
               int ma = i;
      @@ -4467,38 +4467,38 @@
                   ma = l;
               if (r < n && nums[r] > nums[ma])
                   ma = r;
      -        // ノードiが最大か、インデックスl、rが境界外の場合、それ以上のヒープ化は不要で終了
      +        // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける
               if (ma == i) {
                   break;
               }
      -        // 二つのノードを交換
      +        // 2 つのノードを交換
               swap(nums[i], nums[ma]);
      -        // 下向きにヒープ化をループ
      +        // ループで上から下へヒープ化
               i = ma;
           }
       }
       
       /* ヒープソート */
       void heapSort(vector<int> &nums) {
      -    // ヒープ構築操作:葉以外のすべてのノードをヒープ化
      +    // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する
           for (int i = nums.size() / 2 - 1; i >= 0; --i) {
               siftDown(nums, nums.size(), i);
           }
      -    // ヒープから最大要素を抽出し、n-1回繰り返す
      +    // ヒープから最大要素を取り出し、n-1 回繰り返す
           for (int i = nums.size() - 1; i > 0; --i) {
      -        // ルートノードを最右葉ノードと交換(最初の要素を最後の要素と交換)
      +        // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換)
               swap(nums[0], nums[i]);
      -        // ルートノードから上から下へヒープ化を開始
      +        // 根ノードを起点に、上から下へヒープ化
               siftDown(nums, i, 0);
           }
       }
       
      -
      heap_sort.java
      /* ヒープの長さは n、ノード i から上から下へヒープ化開始 */
      +
      heap_sort.java
      /* ヒープの長さは n。ノード i から下方向にヒープ化 */
       void siftDown(int[] nums, int n, int i) {
           while (true) {
      -        // i, l, r の中で最大のノードを判定し、ma とする
      +        // ノード i, l, r のうち値が最大のノードを ma とする
               int l = 2 * i + 1;
               int r = 2 * i + 2;
               int ma = i;
      @@ -4506,103 +4506,452 @@
                   ma = l;
               if (r < n && nums[r] > nums[ma])
                   ma = r;
      -        // ノード i が最大、またはインデックス l, r が範囲外の場合、さらなるヒープ化は不要、ブレーク
      +        // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける
               if (ma == i)
                   break;
      -        // 2つのノードを交換
      +        // 2 つのノードを交換
               int temp = nums[i];
               nums[i] = nums[ma];
               nums[ma] = temp;
      -        // 下向きにヒープ化をループ
      +        // ループで上から下へヒープ化
               i = ma;
           }
       }
       
       /* ヒープソート */
       void heapSort(int[] nums) {
      -    // ヒープ構築操作: 葉ノード以外のすべてのノードをヒープ化
      +    // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する
           for (int i = nums.length / 2 - 1; i >= 0; i--) {
               siftDown(nums, nums.length, i);
           }
      -    // ヒープから最大要素を抽出し、n-1 回繰り返し
      +    // ヒープから最大要素を取り出し、n-1 回繰り返す
           for (int i = nums.length - 1; i > 0; i--) {
      -        // ルートノードと最も右の葉ノードを交換(最初の要素と最後の要素を交換)
      +        // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換)
               int tmp = nums[0];
               nums[0] = nums[i];
               nums[i] = tmp;
      -        // ルートノードから上から下へヒープ化開始
      +        // 根ノードを起点に、上から下へヒープ化
               siftDown(nums, i, 0);
           }
       }
       
      -
      heap_sort.cs
      [class]{heap_sort}-[func]{SiftDown}
      -
      -[class]{heap_sort}-[func]{HeapSort}
      +
      heap_sort.cs
      /* ヒープの長さは n。ノード i から下方向にヒープ化 */
      +void SiftDown(int[] nums, int n, int i) {
      +    while (true) {
      +        // ノード i, l, r のうち値が最大のノードを ma とする
      +        int l = 2 * i + 1;
      +        int r = 2 * i + 2;
      +        int ma = i;
      +        if (l < n && nums[l] > nums[ma])
      +            ma = l;
      +        if (r < n && nums[r] > nums[ma])
      +            ma = r;
      +        // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける
      +        if (ma == i)
      +            break;
      +        // 2 つのノードを交換
      +        (nums[ma], nums[i]) = (nums[i], nums[ma]);
      +        // ループで上から下へヒープ化
      +        i = ma;
      +    }
      +}
      +
      +/* ヒープソート */
      +void HeapSort(int[] nums) {
      +    // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する
      +    for (int i = nums.Length / 2 - 1; i >= 0; i--) {
      +        SiftDown(nums, nums.Length, i);
      +    }
      +    // ヒープから最大要素を取り出し、n-1 回繰り返す
      +    for (int i = nums.Length - 1; i > 0; i--) {
      +        // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換)
      +        (nums[i], nums[0]) = (nums[0], nums[i]);
      +        // 根ノードを起点に、上から下へヒープ化
      +        SiftDown(nums, i, 0);
      +    }
      +}
       
      -
      heap_sort.go
      [class]{}-[func]{siftDown}
      -
      -[class]{}-[func]{heapSort}
      +
      heap_sort.go
      /* ヒープの長さは n。ノード i から下方向にヒープ化 */
      +func siftDown(nums *[]int, n, i int) {
      +    for true {
      +        // ノード i, l, r のうち値が最大のノードを ma とする
      +        l := 2*i + 1
      +        r := 2*i + 2
      +        ma := i
      +        if l < n && (*nums)[l] > (*nums)[ma] {
      +            ma = l
      +        }
      +        if r < n && (*nums)[r] > (*nums)[ma] {
      +            ma = r
      +        }
      +        // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける
      +        if ma == i {
      +            break
      +        }
      +        // 2 つのノードを交換
      +        (*nums)[i], (*nums)[ma] = (*nums)[ma], (*nums)[i]
      +        // ループで上から下へヒープ化
      +        i = ma
      +    }
      +}
      +
      +/* ヒープソート */
      +func heapSort(nums *[]int) {
      +    // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する
      +    for i := len(*nums)/2 - 1; i >= 0; i-- {
      +        siftDown(nums, len(*nums), i)
      +    }
      +    // ヒープから最大要素を取り出し、n-1 回繰り返す
      +    for i := len(*nums) - 1; i > 0; i-- {
      +        // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換)
      +        (*nums)[0], (*nums)[i] = (*nums)[i], (*nums)[0]
      +        // 根ノードを起点に、上から下へヒープ化
      +        siftDown(nums, i, 0)
      +    }
      +}
       
      -
      heap_sort.swift
      [class]{}-[func]{siftDown}
      -
      -[class]{}-[func]{heapSort}
      +
      heap_sort.swift
      /* ヒープの長さは n。ノード i から下方向にヒープ化 */
      +func siftDown(nums: inout [Int], n: Int, i: Int) {
      +    var i = i
      +    while true {
      +        // ノード i, l, r のうち値が最大のノードを ma とする
      +        let l = 2 * i + 1
      +        let r = 2 * i + 2
      +        var ma = i
      +        if l < n, nums[l] > nums[ma] {
      +            ma = l
      +        }
      +        if r < n, nums[r] > nums[ma] {
      +            ma = r
      +        }
      +        // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける
      +        if ma == i {
      +            break
      +        }
      +        // 2 つのノードを交換
      +        nums.swapAt(i, ma)
      +        // ループで上から下へヒープ化
      +        i = ma
      +    }
      +}
      +
      +/* ヒープソート */
      +func heapSort(nums: inout [Int]) {
      +    // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する
      +    for i in stride(from: nums.count / 2 - 1, through: 0, by: -1) {
      +        siftDown(nums: &nums, n: nums.count, i: i)
      +    }
      +    // ヒープから最大要素を取り出し、n-1 回繰り返す
      +    for i in nums.indices.dropFirst().reversed() {
      +        // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換)
      +        nums.swapAt(0, i)
      +        // 根ノードを起点に、上から下へヒープ化
      +        siftDown(nums: &nums, n: i, i: 0)
      +    }
      +}
       
      -
      heap_sort.js
      [class]{}-[func]{siftDown}
      -
      -[class]{}-[func]{heapSort}
      +
      heap_sort.js
      /* ヒープの長さは n。ノード i から下方向にヒープ化 */
      +function siftDown(nums, n, i) {
      +    while (true) {
      +        // ノード i, l, r のうち値が最大のノードを ma とする
      +        let l = 2 * i + 1;
      +        let r = 2 * i + 2;
      +        let ma = i;
      +        if (l < n && nums[l] > nums[ma]) {
      +            ma = l;
      +        }
      +        if (r < n && nums[r] > nums[ma]) {
      +            ma = r;
      +        }
      +        // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける
      +        if (ma === i) {
      +            break;
      +        }
      +        // 2 つのノードを交換
      +        [nums[i], nums[ma]] = [nums[ma], nums[i]];
      +        // ループで上から下へヒープ化
      +        i = ma;
      +    }
      +}
      +
      +/* ヒープソート */
      +function heapSort(nums) {
      +    // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する
      +    for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) {
      +        siftDown(nums, nums.length, i);
      +    }
      +    // ヒープから最大要素を取り出し、n-1 回繰り返す
      +    for (let i = nums.length - 1; i > 0; i--) {
      +        // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換)
      +        [nums[0], nums[i]] = [nums[i], nums[0]];
      +        // 根ノードを起点に、上から下へヒープ化
      +        siftDown(nums, i, 0);
      +    }
      +}
       
      -
      heap_sort.ts
      [class]{}-[func]{siftDown}
      -
      -[class]{}-[func]{heapSort}
      +
      heap_sort.ts
      /* ヒープの長さは n。ノード i から下方向にヒープ化 */
      +function siftDown(nums: number[], n: number, i: number): void {
      +    while (true) {
      +        // ノード i, l, r のうち値が最大のノードを ma とする
      +        let l = 2 * i + 1;
      +        let r = 2 * i + 2;
      +        let ma = i;
      +        if (l < n && nums[l] > nums[ma]) {
      +            ma = l;
      +        }
      +        if (r < n && nums[r] > nums[ma]) {
      +            ma = r;
      +        }
      +        // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける
      +        if (ma === i) {
      +            break;
      +        }
      +        // 2 つのノードを交換
      +        [nums[i], nums[ma]] = [nums[ma], nums[i]];
      +        // ループで上から下へヒープ化
      +        i = ma;
      +    }
      +}
      +
      +/* ヒープソート */
      +function heapSort(nums: number[]): void {
      +    // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する
      +    for (let i = Math.floor(nums.length / 2) - 1; i >= 0; i--) {
      +        siftDown(nums, nums.length, i);
      +    }
      +    // ヒープから最大要素を取り出し、n-1 回繰り返す
      +    for (let i = nums.length - 1; i > 0; i--) {
      +        // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換)
      +        [nums[0], nums[i]] = [nums[i], nums[0]];
      +        // 根ノードを起点に、上から下へヒープ化
      +        siftDown(nums, i, 0);
      +    }
      +}
       
      -
      heap_sort.dart
      [class]{}-[func]{siftDown}
      -
      -[class]{}-[func]{heapSort}
      +
      heap_sort.dart
      /* ヒープの長さは n。ノード i から下方向にヒープ化 */
      +void siftDown(List<int> nums, int n, int i) {
      +  while (true) {
      +    // ノード i, l, r のうち値が最大のノードを ma とする
      +    int l = 2 * i + 1;
      +    int r = 2 * i + 2;
      +    int ma = i;
      +    if (l < n && nums[l] > nums[ma]) ma = l;
      +    if (r < n && nums[r] > nums[ma]) ma = r;
      +    // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける
      +    if (ma == i) break;
      +    // 2 つのノードを交換
      +    int temp = nums[i];
      +    nums[i] = nums[ma];
      +    nums[ma] = temp;
      +    // ループで上から下へヒープ化
      +    i = ma;
      +  }
      +}
      +
      +/* ヒープソート */
      +void heapSort(List<int> nums) {
      +  // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する
      +  for (int i = nums.length ~/ 2 - 1; i >= 0; i--) {
      +    siftDown(nums, nums.length, i);
      +  }
      +  // ヒープから最大要素を取り出し、n-1 回繰り返す
      +  for (int i = nums.length - 1; i > 0; i--) {
      +    // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換)
      +    int tmp = nums[0];
      +    nums[0] = nums[i];
      +    nums[i] = tmp;
      +    // 根ノードを起点に、上から下へヒープ化
      +    siftDown(nums, i, 0);
      +  }
      +}
       
      -
      heap_sort.rs
      [class]{}-[func]{sift_down}
      -
      -[class]{}-[func]{heap_sort}
      +
      heap_sort.rs
      /* ヒープの長さは n。ノード i から下方向にヒープ化 */
      +fn sift_down(nums: &mut [i32], n: usize, mut i: usize) {
      +    loop {
      +        // ノード i, l, r のうち値が最大のノードを ma とする
      +        let l = 2 * i + 1;
      +        let r = 2 * i + 2;
      +        let mut ma = i;
      +        if l < n && nums[l] > nums[ma] {
      +            ma = l;
      +        }
      +        if r < n && nums[r] > nums[ma] {
      +            ma = r;
      +        }
      +        // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける
      +        if ma == i {
      +            break;
      +        }
      +        // 2 つのノードを交換
      +        nums.swap(i, ma);
      +        // ループで上から下へヒープ化
      +        i = ma;
      +    }
      +}
      +
      +/* ヒープソート */
      +fn heap_sort(nums: &mut [i32]) {
      +    // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する
      +    for i in (0..nums.len() / 2).rev() {
      +        sift_down(nums, nums.len(), i);
      +    }
      +    // ヒープから最大要素を取り出し、n-1 回繰り返す
      +    for i in (1..nums.len()).rev() {
      +        // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換)
      +        nums.swap(0, i);
      +        // 根ノードを起点に、上から下へヒープ化
      +        sift_down(nums, i, 0);
      +    }
      +}
       
      -
      heap_sort.c
      [class]{}-[func]{siftDown}
      -
      -[class]{}-[func]{heapSort}
      +
      heap_sort.c
      /* ヒープの長さは n。ノード i から下方向にヒープ化 */
      +void siftDown(int nums[], int n, int i) {
      +    while (1) {
      +        // ノード i, l, r のうち値が最大のノードを ma とする
      +        int l = 2 * i + 1;
      +        int r = 2 * i + 2;
      +        int ma = i;
      +        if (l < n && nums[l] > nums[ma])
      +            ma = l;
      +        if (r < n && nums[r] > nums[ma])
      +            ma = r;
      +        // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける
      +        if (ma == i) {
      +            break;
      +        }
      +        // 2 つのノードを交換
      +        int temp = nums[i];
      +        nums[i] = nums[ma];
      +        nums[ma] = temp;
      +        // ループで上から下へヒープ化
      +        i = ma;
      +    }
      +}
      +
      +/* ヒープソート */
      +void heapSort(int nums[], int n) {
      +    // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する
      +    for (int i = n / 2 - 1; i >= 0; --i) {
      +        siftDown(nums, n, i);
      +    }
      +    // ヒープから最大要素を取り出し、n-1 回繰り返す
      +    for (int i = n - 1; i > 0; --i) {
      +        // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換)
      +        int tmp = nums[0];
      +        nums[0] = nums[i];
      +        nums[i] = tmp;
      +        // 根ノードを起点に、上から下へヒープ化
      +        siftDown(nums, i, 0);
      +    }
      +}
       
      -
      heap_sort.kt
      [class]{}-[func]{siftDown}
      -
      -[class]{}-[func]{heapSort}
      +
      heap_sort.kt
      /* ヒープの長さは n。ノード i から下方向にヒープ化 */
      +fun siftDown(nums: IntArray, n: Int, li: Int) {
      +    var i = li
      +    while (true) {
      +        // ノード i, l, r のうち値が最大のノードを ma とする
      +        val l = 2 * i + 1
      +        val r = 2 * i + 2
      +        var ma = i
      +        if (l < n && nums[l] > nums[ma]) 
      +            ma = l
      +        if (r < n && nums[r] > nums[ma]) 
      +            ma = r
      +        // ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける
      +        if (ma == i) 
      +            break
      +        // 2 つのノードを交換
      +        val temp = nums[i]
      +        nums[i] = nums[ma]
      +        nums[ma] = temp
      +        // ループで上から下へヒープ化
      +        i = ma
      +    }
      +}
      +
      +/* ヒープソート */
      +fun heapSort(nums: IntArray) {
      +    // ヒープ構築:葉ノード以外のすべてのノードをヒープ化する
      +    for (i in nums.size / 2 - 1 downTo 0) {
      +        siftDown(nums, nums.size, i)
      +    }
      +    // ヒープから最大要素を取り出し、n-1 回繰り返す
      +    for (i in nums.size - 1 downTo 1) {
      +        // 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換)
      +        val temp = nums[0]
      +        nums[0] = nums[i]
      +        nums[i] = temp
      +        // 根ノードを起点に、上から下へヒープ化
      +        siftDown(nums, i, 0)
      +    }
      +}
       
      -
      heap_sort.rb
      [class]{}-[func]{sift_down}
      -
      -[class]{}-[func]{heap_sort}
      +
      heap_sort.rb
      ### ヒープ長 n で、ノード i から上から下へヒープ化 ###
      +def sift_down(nums, n, i)
      +  while true
      +    # ノード i, l, r のうち値が最大のノードを ma とする
      +    l = 2 * i + 1
      +    r = 2 * i + 2
      +    ma = i
      +    ma = l if l < n && nums[l] > nums[ma]
      +    ma = r if r < n && nums[r] > nums[ma]
      +    # ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける
      +    break if ma == i
      +    # 2 つのノードを交換
      +    nums[i], nums[ma] = nums[ma], nums[i]
      +    # ループで上から下へヒープ化
      +    i = ma
      +  end
      +end
      +
      +### ヒープソート ###
      +def heap_sort(nums)
      +  # ヒープ構築:葉ノード以外のすべてのノードをヒープ化する
      +  (nums.length / 2 - 1).downto(0) do |i|
      +    sift_down(nums, nums.length, i)
      +  end
      +  # ヒープから最大要素を取り出し、n-1 回繰り返す
      +  (nums.length - 1).downto(1) do |i|
      +    # 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換)
      +    nums[0], nums[i] = nums[i], nums[0]
      +    # 根ノードを起点に、上から下へヒープ化
      +    sift_down(nums, i, 0)
      +  end
      +end
       
      -

      11.7.2   アルゴリズムの特徴

      +
      +コードの可視化 +

      +

      +
      +

      11.7.2   アルゴリズムの特性

        -
      • 時間計算量は \(O(n \log n)\)、非適応ソート:ヒープの構築は \(O(n)\) 時間を使用します。ヒープから最大要素を抽出するには \(O(\log n)\) 時間がかかり、\(n - 1\) ラウンドループします。
      • -
      • 空間計算量は \(O(1)\)、インプレースソート:いくつかのポインタ変数が \(O(1)\) 空間を使用します。要素の交換とヒープ化操作は元の配列で実行されます。
      • -
      • 非安定ソート:ヒープの頂上と底部要素の交換中に、等しい要素の相対位置が変わる可能性があります。
      • +
      • 時間計算量は \(O(n \log n)\)、非適応ソート:ヒープ構築操作には \(O(n)\) の時間がかかります。ヒープから最大要素を取り出す時間計算量は \(O(\log n)\) であり、これを合計 \(n - 1\) 回繰り返します。
      • +
      • 空間計算量は \(O(1)\)、インプレースソート:いくつかのポインタ変数が使う空間は \(O(1)\) です。要素の交換とヒープ化操作はいずれも元の配列上で行われます。
      • +
      • 非安定ソート:ヒープ頂点の要素とヒープ末尾の要素を交換する際、等しい要素どうしの相対位置が変化する可能性があります。
      diff --git a/ja/chapter_sorting/index.html b/ja/chapter_sorting/index.html index db5d7d0db..eca36a351 100644 --- a/ja/chapter_sorting/index.html +++ b/ja/chapter_sorting/index.html @@ -6,7 +6,7 @@ - + @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1591,7 +1591,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1613,7 +1613,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1635,7 +1635,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1942,7 +1942,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2105,7 +2105,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2491,7 +2491,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2547,7 +2547,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2575,7 +2575,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -3115,7 +3115,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3171,7 +3171,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3199,7 +3199,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3392,7 +3392,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3420,7 +3420,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3561,7 +3561,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3589,7 +3589,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3617,7 +3617,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3645,7 +3645,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3838,7 +3838,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4083,7 +4083,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4274,11 +4274,11 @@

      第 11 章   ソート

      -

      Sorting

      +

      ソート

      Abstract

      -

      ソートは混沌を秩序に変える魔法の鍵のようなもので、データをより効率的に理解し処理することを可能にします。

      -

      単純な昇順であろうと複雑なカテゴリ配列であろうと、ソートはデータの調和美を明らかにします。

      +

      ソートは、混沌を秩序へと変える魔法の鍵のようなものであり、データをより効率的に理解し処理することを可能にします。

      +

      単純な昇順であれ、複雑な分類配列であれ、ソートはデータの調和のとれた美しさを私たちに示してくれます。

      章の内容

        diff --git a/ja/chapter_sorting/insertion_sort/index.html b/ja/chapter_sorting/insertion_sort/index.html index 4635819cd..7c8994e99 100644 --- a/ja/chapter_sorting/insertion_sort/index.html +++ b/ja/chapter_sorting/insertion_sort/index.html @@ -6,7 +6,7 @@ - + @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1591,7 +1591,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1613,7 +1613,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1635,7 +1635,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1942,7 +1942,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2105,7 +2105,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2491,7 +2491,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2547,7 +2547,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2575,7 +2575,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -2867,7 +2867,7 @@ - 11.4.1   アルゴリズムプロセス + 11.4.1   アルゴリズムの流れ @@ -2878,7 +2878,7 @@ - 11.4.2   アルゴリズムの特性 + 11.4.2   アルゴリズムの特徴 @@ -3196,7 +3196,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3252,7 +3252,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3280,7 +3280,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3473,7 +3473,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3501,7 +3501,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3642,7 +3642,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3670,7 +3670,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3698,7 +3698,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3726,7 +3726,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3919,7 +3919,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4164,7 +4164,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4310,7 +4310,7 @@ - 11.4.1   アルゴリズムプロセス + 11.4.1   アルゴリズムの流れ @@ -4321,7 +4321,7 @@ - 11.4.2   アルゴリズムの特性 + 11.4.2   アルゴリズムの特徴 @@ -4377,52 +4377,52 @@

      11.4   挿入ソート

      -

      挿入ソートは、トランプのデッキを手動でソートするプロセスによく似た動作をするシンプルなソートアルゴリズムです。

      -

      具体的には、未ソート区間からベース要素を選択し、その左側のソート済み区間の要素と比較して、要素を正しい位置に挿入します。

      -

      下図は、要素が配列に挿入される方法を示しています。ベース要素をbaseとすると、ターゲットインデックスからbaseまでのすべての要素を右に1つずつシフトし、その後baseをターゲットインデックスに割り当てる必要があります。

      -

      Single insertion operation

      -

      図 11-6   Single insertion operation

      +

      挿入ソート(insertion sort)は単純なソートアルゴリズムであり、その動作原理は手作業でトランプの山を整える過程と非常によく似ています。

      +

      具体的には、未ソート区間から基準要素を 1 つ選び、その要素を左側の整列済み区間の要素と 1 つずつ比較し、正しい位置に挿入します。

      +

      以下の図は、配列に要素を挿入する操作の流れを示しています。基準要素を base とすると、目的のインデックスから base までのすべての要素を 1 つずつ右に移動し、その後 base を目的のインデックスに代入する必要があります。

      +

      1 回の挿入操作

      +

      図 11-6   1 回の挿入操作

      -

      11.4.1   アルゴリズムプロセス

      -

      挿入ソートの全体的なプロセスは下図に示されます。

      +

      11.4.1   アルゴリズムの流れ

      +

      挿入ソート全体の流れを以下の図に示します。

        -
      1. 配列の最初の要素をソート済みとみなします。
      2. -
      3. 2番目の要素をbaseとして選択し、正しい位置に挿入して、最初の2つの要素をソート済みにします
      4. -
      5. 3番目の要素をbaseとして選択し、正しい位置に挿入して、最初の3つの要素をソート済みにします
      6. -
      7. この方法で続行し、最後の反復では、最後の要素をbaseとして取り、正しい位置に挿入した後、すべての要素がソートされます
      8. +
      9. 初期状態では、配列の 1 番目の要素はすでに整列済みです。
      10. +
      11. 配列の 2 番目の要素を base として選び、正しい位置に挿入すると、**配列の先頭 2 要素が整列済み**になります。
      12. +
      13. 3 番目の要素を base として選び、正しい位置に挿入すると、**配列の先頭 3 要素が整列済み**になります。
      14. +
      15. このように繰り返し、最後のラウンドで最後の要素を base として選んで正しい位置に挿入すると、**すべての要素が整列済み**になります。
      -

      Insertion sort process

      -

      図 11-7   Insertion sort process

      +

      挿入ソートの流れ

      +

      図 11-7   挿入ソートの流れ

      -

      コード例は以下の通りです:

      +

      コード例は以下のとおりです。

      insertion_sort.py
      def insertion_sort(nums: list[int]):
           """挿入ソート"""
      -    # 外側のループ:ソート済み範囲は [0, i-1]
      +    # 外側ループ:整列済み区間は [0, i-1]
           for i in range(1, len(nums)):
               base = nums[i]
               j = i - 1
      -        # 内側のループ:base をソート済み範囲 [0, i-1] の正しい位置に挿入
      +        # 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する
               while j >= 0 and nums[j] > base:
      -            nums[j + 1] = nums[j]  # nums[j] を右に1つ移動
      +            nums[j + 1] = nums[j]  # nums[j] を 1 つ右へ移動する
                   j -= 1
      -        nums[j + 1] = base  # base を正しい位置に代入
      +        nums[j + 1] = base  # base を正しい位置に配置する
       
      insertion_sort.cpp
      /* 挿入ソート */
       void insertionSort(vector<int> &nums) {
      -    // 外側ループ:ソート済み範囲は[0, i-1]
      +    // 外側ループ:整列済み区間は [0, i-1]
           for (int i = 1; i < nums.size(); i++) {
               int base = nums[i], j = i - 1;
      -        // 内側ループ:baseをソート済み範囲[0, i-1]内の正しい位置に挿入
      +        // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する
               while (j >= 0 && nums[j] > base) {
      -            nums[j + 1] = nums[j]; // nums[j]を一つ右に移動
      +            nums[j + 1] = nums[j]; // nums[j] を 1 つ右へ移動する
                   j--;
               }
      -        nums[j + 1] = base; // baseを正しい位置に代入
      +        nums[j + 1] = base; // base を正しい位置に配置する
           }
       }
       
      @@ -4430,76 +4430,210 @@
      insertion_sort.java
      /* 挿入ソート */
       void insertionSort(int[] nums) {
      -    // 外側ループ: ソート済み範囲は [0, i-1]
      +    // 外側ループ:整列済み区間は [0, i-1]
           for (int i = 1; i < nums.length; i++) {
               int base = nums[i], j = i - 1;
      -        // 内側ループ: base をソート済み範囲 [0, i-1] の正しい位置に挿入
      +        // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する
               while (j >= 0 && nums[j] > base) {
      -            nums[j + 1] = nums[j]; // nums[j] を右に1つ移動
      +            nums[j + 1] = nums[j]; // nums[j] を 1 つ右へ移動する
                   j--;
               }
      -        nums[j + 1] = base;        // base を正しい位置に代入
      +        nums[j + 1] = base;        // base を正しい位置に配置する
           }
       }
       
      -
      insertion_sort.cs
      [class]{insertion_sort}-[func]{InsertionSort}
      +
      insertion_sort.cs
      /* 挿入ソート */
      +void InsertionSort(int[] nums) {
      +    // 外側ループ:整列済み区間は [0, i-1]
      +    for (int i = 1; i < nums.Length; i++) {
      +        int bas = nums[i], j = i - 1;
      +        // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する
      +        while (j >= 0 && nums[j] > bas) {
      +            nums[j + 1] = nums[j]; // nums[j] を 1 つ右へ移動する
      +            j--;
      +        }
      +        nums[j + 1] = bas;         // base を正しい位置に配置する
      +    }
      +}
       
      -
      insertion_sort.go
      [class]{}-[func]{insertionSort}
      +
      insertion_sort.go
      /* 挿入ソート */
      +func insertionSort(nums []int) {
      +    // 外側ループ:整列済み区間は [0, i-1]
      +    for i := 1; i < len(nums); i++ {
      +        base := nums[i]
      +        j := i - 1
      +        // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する
      +        for j >= 0 && nums[j] > base {
      +            nums[j+1] = nums[j] // nums[j] を 1 つ右へ移動する
      +            j--
      +        }
      +        nums[j+1] = base // base を正しい位置に配置する
      +    }
      +}
       
      -
      insertion_sort.swift
      [class]{}-[func]{insertionSort}
      +
      insertion_sort.swift
      /* 挿入ソート */
      +func insertionSort(nums: inout [Int]) {
      +    // 外側ループ:整列済み区間は [0, i-1]
      +    for i in nums.indices.dropFirst() {
      +        let base = nums[i]
      +        var j = i - 1
      +        // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する
      +        while j >= 0, nums[j] > base {
      +            nums[j + 1] = nums[j] // nums[j] を 1 つ右へ移動する
      +            j -= 1
      +        }
      +        nums[j + 1] = base // base を正しい位置に配置する
      +    }
      +}
       
      -
      insertion_sort.js
      [class]{}-[func]{insertionSort}
      +
      insertion_sort.js
      /* 挿入ソート */
      +function insertionSort(nums) {
      +    // 外側ループ:整列済み区間は [0, i-1]
      +    for (let i = 1; i < nums.length; i++) {
      +        let base = nums[i],
      +            j = i - 1;
      +        // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する
      +        while (j >= 0 && nums[j] > base) {
      +            nums[j + 1] = nums[j]; // nums[j] を 1 つ右へ移動する
      +            j--;
      +        }
      +        nums[j + 1] = base; // base を正しい位置に配置する
      +    }
      +}
       
      -
      insertion_sort.ts
      [class]{}-[func]{insertionSort}
      +
      insertion_sort.ts
      /* 挿入ソート */
      +function insertionSort(nums: number[]): void {
      +    // 外側ループ:整列済み区間は [0, i-1]
      +    for (let i = 1; i < nums.length; i++) {
      +        const base = nums[i];
      +        let j = i - 1;
      +        // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する
      +        while (j >= 0 && nums[j] > base) {
      +            nums[j + 1] = nums[j]; // nums[j] を 1 つ右へ移動する
      +            j--;
      +        }
      +        nums[j + 1] = base; // base を正しい位置に配置する
      +    }
      +}
       
      -
      insertion_sort.dart
      [class]{}-[func]{insertionSort}
      +
      insertion_sort.dart
      /* 挿入ソート */
      +void insertionSort(List<int> nums) {
      +  // 外側ループ:整列済み区間は [0, i-1]
      +  for (int i = 1; i < nums.length; i++) {
      +    int base = nums[i], j = i - 1;
      +    // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する
      +    while (j >= 0 && nums[j] > base) {
      +      nums[j + 1] = nums[j]; // nums[j] を 1 つ右へ移動する
      +      j--;
      +    }
      +    nums[j + 1] = base; // base を正しい位置に配置する
      +  }
      +}
       
      -
      insertion_sort.rs
      [class]{}-[func]{insertion_sort}
      +
      insertion_sort.rs
      /* 挿入ソート */
      +fn insertion_sort(nums: &mut [i32]) {
      +    // 外側ループ:整列済み区間は [0, i-1]
      +    for i in 1..nums.len() {
      +        let (base, mut j) = (nums[i], (i - 1) as i32);
      +        // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する
      +        while j >= 0 && nums[j as usize] > base {
      +            nums[(j + 1) as usize] = nums[j as usize]; // nums[j] を 1 つ右へ移動する
      +            j -= 1;
      +        }
      +        nums[(j + 1) as usize] = base; // base を正しい位置に配置する
      +    }
      +}
       
      -
      insertion_sort.c
      [class]{}-[func]{insertionSort}
      +
      insertion_sort.c
      /* 挿入ソート */
      +void insertionSort(int nums[], int size) {
      +    // 外側ループ:整列済み区間は [0, i-1]
      +    for (int i = 1; i < size; i++) {
      +        int base = nums[i], j = i - 1;
      +        // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する
      +        while (j >= 0 && nums[j] > base) {
      +            // nums[j] を 1 つ右へ移動する
      +            nums[j + 1] = nums[j];
      +            j--;
      +        }
      +        // base を正しい位置に配置する
      +        nums[j + 1] = base;
      +    }
      +}
       
      -
      insertion_sort.kt
      [class]{}-[func]{insertionSort}
      +
      insertion_sort.kt
      /* 挿入ソート */
      +fun insertionSort(nums: IntArray) {
      +    // 外側ループ: ソート済み要素は 1, 2, ..., n
      +    for (i in nums.indices) {
      +        val base = nums[i]
      +        var j = i - 1
      +        // 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する
      +        while (j >= 0 && nums[j] > base) {
      +            nums[j + 1] = nums[j] // nums[j] を 1 つ右へ移動する
      +            j--
      +        }
      +        nums[j + 1] = base        // base を正しい位置に配置する
      +    }
      +}
       
      -
      insertion_sort.rb
      [class]{}-[func]{insertion_sort}
      +
      insertion_sort.rb
      ### 挿入ソート ###
      +def insertion_sort(nums)
      +  n = nums.length
      +  # 外側ループ:整列済み区間は [0, i-1]
      +  for i in 1...n
      +    base = nums[i]
      +    j = i - 1
      +    # 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する
      +    while j >= 0 && nums[j] > base
      +      nums[j + 1] = nums[j] # nums[j] を 1 つ右へ移動する
      +      j -= 1
      +    end
      +    nums[j + 1] = base # base を正しい位置に配置する
      +  end
      +end
       
      -

      11.4.2   アルゴリズムの特性

      +
      +コードの可視化 +

      +

      +
      +

      11.4.2   アルゴリズムの特徴

        -
      • 時間計算量は\(O(n^2)\)、適応ソート:最悪の場合、各挿入操作には\(n - 1\)\(n-2\)、...、\(2\)\(1\)のループが必要で、合計は\((n - 1) n / 2\)となり、時間計算量は\(O(n^2)\)です。順序付きデータの場合、挿入操作は早期に終了します。入力配列が完全に順序付けられている場合、挿入ソートは最良時間計算量\(O(n)\)を実現します。
      • -
      • 空間計算量は\(O(1)\)、インプレースソート:ポインタ\(i\)\(j\)は定数量の追加空間を使用します。
      • -
      • 安定ソート:挿入操作中、等しい要素の右側に要素を挿入し、順序を変更しません。
      • +
      • 計算量は \(O(n^2)\)、適応的ソート:最悪の場合、各挿入操作ではそれぞれ \(n - 1\)\(n-2\)\(\dots\)\(2\)\(1\) 回のループが必要であり、合計は \((n - 1) n / 2\) となるため、時間計算量は \(O(n^2)\) です。データが整列済みであれば、挿入操作は早期に終了します。入力配列が完全に整列済みである場合、挿入ソートは最良の時間計算量 \(O(n)\) に達します。
      • +
      • 空間計算量は \(O(1)\)、インプレースソート:ポインタ \(i\)\(j\) は定数サイズの追加領域しか使用しません。
      • +
      • 安定ソート:挿入操作の過程では、要素を等しい要素の右側に挿入するため、それらの順序は変化しません。

      11.4.3   挿入ソートの利点

      -

      挿入ソートの時間計算量は\(O(n^2)\)で、次に学習するクイックソートの時間計算量は\(O(n \log n)\)です。挿入ソートはより高い時間計算量を持ちますが、小さな入力サイズでは通常より高速です

      -

      この結論は線形探索と二分探索の結論と似ています。時間計算量が\(O(n \log n)\)で分割統治戦略に基づくクイックソートなどのアルゴリズムは、多くの場合より多くの単位操作を含みます。小さな入力サイズでは、\(n^2\)\(n \log n\)の数値は近く、計算量が支配的でなく、ラウンドあたりの単位操作数が決定的な役割を果たします。

      -

      実際、多くのプログラミング言語(Javaなど)は、組み込みソート関数内で挿入ソートを使用しています。一般的なアプローチは:長い配列に対しては、クイックソートなどの分割統治戦略に基づくソートアルゴリズムを使用し、短い配列に対しては挿入ソートを直接使用します。

      -

      バブルソート、選択ソート、挿入ソートはすべて時間計算量\(O(n^2)\)を持ちますが、実際には、挿入ソートはバブルソートや選択ソートよりも一般的に使用されます。主な理由は以下の通りです。

      +

      挿入ソートの時間計算量は \(O(n^2)\) であり、これから学ぶクイックソートの時間計算量は \(O(n \log n)\) です。挿入ソートの時間計算量のほうが大きいにもかかわらず、**データ量が小さい場合には、挿入ソートのほうが通常は高速**です。

      +

      この結論は、線形探索と二分探索の適用条件に関する結論と似ています。クイックソートのような \(O(n \log n)\) のアルゴリズムは分割統治法に基づくソートアルゴリズムであり、一般により多くの基本演算を含みます。一方、データ量が小さい場合は、\(n^2\)\(n \log n\) の値は比較的近く、計算量が支配的ではなくなり、各ラウンドにおける基本演算の回数が決定的な役割を果たします。

      +

      実際、多くのプログラミング言語(たとえば Java)の組み込みソート関数では挿入ソートが採用されており、その大まかな考え方は次のとおりです。長い配列にはクイックソートなどの分割統治法に基づくソートアルゴリズムを使い、短い配列には直接挿入ソートを使います。

      +

      バブルソート、選択ソート、挿入ソートはいずれも時間計算量が \(O(n^2)\) ですが、実際には、挿入ソートはバブルソートや選択ソートよりもはるかに高い頻度で使われます。主な理由は次のとおりです。

        -
      • バブルソートは要素交換に基づき、一時変数の使用が必要で、3つの単位操作を含みます;挿入ソートは要素代入に基づき、1つの単位操作のみが必要です。したがって、バブルソートの計算オーバーヘッドは一般的に挿入ソートよりも高くなります
      • -
      • 選択ソートの時間計算量は常に\(O(n^2)\)です。部分的に順序付けられたデータのセットが与えられた場合、挿入ソートは通常選択ソートよりも効率的です
      • -
      • 選択ソートは不安定で、マルチレベルソートに適用できません。
      • +
      • バブルソートは要素の交換によって実装され、1 つの一時変数を必要とするため、合計で 3 回の基本演算が関わります。これに対して、挿入ソートは要素の代入に基づいており、必要な基本演算は 1 回だけです。したがって、バブルソートの計算コストは通常、挿入ソートより高くなります
      • +
      • 選択ソートの時間計算量はどのような場合でも \(O(n^2)\) です。**部分的に整列されたデータが与えられた場合、挿入ソートは通常、選択ソートより効率的**です。
      • +
      • 選択ソートは安定ではないため、多段ソートには適用できません。
      diff --git a/ja/chapter_sorting/merge_sort/index.html b/ja/chapter_sorting/merge_sort/index.html index 7759a8db4..f6461d09b 100644 --- a/ja/chapter_sorting/merge_sort/index.html +++ b/ja/chapter_sorting/merge_sort/index.html @@ -6,7 +6,7 @@ - + @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1591,7 +1591,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1613,7 +1613,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1635,7 +1635,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1942,7 +1942,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2105,7 +2105,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2491,7 +2491,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2547,7 +2547,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2575,7 +2575,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -2923,7 +2923,7 @@ - 11.6.1   アルゴリズムワークフロー + 11.6.1   アルゴリズムの流れ @@ -3196,7 +3196,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3252,7 +3252,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3280,7 +3280,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3473,7 +3473,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3501,7 +3501,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3642,7 +3642,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3670,7 +3670,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3698,7 +3698,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3726,7 +3726,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3919,7 +3919,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4164,7 +4164,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4310,7 +4310,7 @@ - 11.6.1   アルゴリズムワークフロー + 11.6.1   アルゴリズムの流れ @@ -4377,25 +4377,25 @@

      11.6   マージソート

      -

      マージソートは分割統治戦略に基づくソートアルゴリズムで、下図に示す「分割」と「マージ」フェーズを含みます。

      +

      マージソート(merge sort)は分割統治戦略に基づくソートアルゴリズムであり、以下の図に示す「分割」と「マージ」の段階から構成されます。

        -
      1. 分割フェーズ:中点から配列を再帰的に分割し、長い配列のソート問題をより短い配列に変換します。
      2. -
      3. マージフェーズ:サブ配列の長さが1になったときに分割を停止し、その後マージを開始します。2つの短いソート済み配列を連続的により長いソート済み配列にマージし、プロセスが完了するまで続行します。
      4. +
      5. 分割段階:再帰によって配列を中点で繰り返し分割し、長い配列のソート問題を短い配列のソート問題へ変換します。
      6. +
      7. マージ段階:部分配列の長さが 1 になったら分割を終了し、マージを開始して、左右 2 つの短いソート済み配列をより長いソート済み配列へと繰り返しマージしていきます。
      -

      The divide and merge phases of merge sort

      -

      図 11-10   The divide and merge phases of merge sort

      +

      マージソートの分割とマージの段階

      +

      図 11-10   マージソートの分割とマージの段階

      -

      11.6.1   アルゴリズムワークフロー

      -

      下図に示すように、「分割フェーズ」は中点から配列を上から下に2つのサブ配列に再帰的に分割します。

      +

      11.6.1   アルゴリズムの流れ

      +

      以下の図に示すように、「分割段階」では配列を上から下へ再帰的に中点で 2 つの部分配列へ分割します。

        -
      1. 中点midを計算し、左サブ配列(区間[left, mid])と右サブ配列(区間[mid + 1, right])を再帰的に分割します。
      2. -
      3. サブ配列の長さが1になるまでステップ1.を再帰的に続行し、その後停止します。
      4. +
      5. 配列の中点 mid を計算し、左部分配列(区間 [left, mid] )と右部分配列(区間 [mid + 1, right] )を再帰的に分割します。
      6. +
      7. 手順 1. を再帰的に実行し、部分配列区間の長さが 1 になった時点で終了します。
      -

      「マージフェーズ」は左と右のサブ配列を下から上にソート済み配列に結合します。重要なのは、マージが長さ1のサブ配列から開始され、マージフェーズ中に各サブ配列がソートされることです。

      +

      「マージ段階」では左部分配列と右部分配列を下から上へとマージし、1 つのソート済み配列にします。長さ 1 の部分配列からマージを始めるため、この段階の各部分配列はすでに整列されています。

      -

      Merge sort process

      +

      マージソートの手順

      merge_sort_step2

      @@ -4426,25 +4426,25 @@
      -

      図 11-11   Merge sort process

      +

      図 11-11   マージソートの手順

      -

      マージソートの再帰順序は二分木の後順横断と一致することが観察できます。

      +

      観察すると、マージソートの再帰順序は二分木の後順走査と一致していることがわかります。

        -
      • 後順横断:まず左のサブツリーを再帰的に横断し、次に右のサブツリーを横断し、最後にルートノードを処理します。
      • -
      • マージソート:まず左のサブ配列を再帰的に処理し、次に右のサブ配列を処理し、最後にマージを実行します。
      • +
      • 後順走査:まず左部分木を再帰し、次に右部分木を再帰し、最後に根ノードを処理します。
      • +
      • マージソート:まず左部分配列を再帰し、次に右部分配列を再帰し、最後にマージを処理します。
      -

      マージソートの実装は以下のコードに示されます。numsでマージされる区間は[left, right]で、tmpの対応する区間は[0, right - left]であることに注意してください。

      +

      マージソートの実装を以下のコードに示します。注意として、nums のマージ対象区間は [left, right] であり、tmp の対応区間は [0, right - left] です。

      merge_sort.py
      def merge(nums: list[int], left: int, mid: int, right: int):
      -    """左サブ配列と右サブ配列をマージ"""
      -    # 左サブ配列区間は [left, mid]、右サブ配列区間は [mid+1, right]
      -    # 一時配列 tmp を作成してマージ結果を格納
      +    """左部分配列と右部分配列をマージ"""
      +    # 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]
      +    # マージ結果を格納する一時配列 tmp を作成
           tmp = [0] * (right - left + 1)
      -    # 左右サブ配列の開始インデックスを初期化
      +    # 左右の部分配列の開始インデックスを初期化する
           i, j, k = left, mid + 1, 0
      -    # 両方のサブ配列に要素が残っている間、より小さい要素を一時配列にコピー
      +    # 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする
           while i <= mid and j <= right:
               if nums[i] <= nums[j]:
                   tmp[k] = nums[i]
      @@ -4453,7 +4453,7 @@
                   tmp[k] = nums[j]
                   j += 1
               k += 1
      -    # 残った左右サブ配列の要素を一時配列にコピー
      +    # 左右の部分配列の残り要素を一時配列にコピーする
           while i <= mid:
               tmp[k] = nums[i]
               i += 1
      @@ -4462,7 +4462,7 @@
               tmp[k] = nums[j]
               j += 1
               k += 1
      -    # 一時配列 tmp の要素を元の配列 nums の対応する区間にコピーバック
      +    # 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする
           for k in range(0, len(tmp)):
               nums[left + k] = tmp[k]
       
      @@ -4470,38 +4470,38 @@
           """マージソート"""
           # 終了条件
           if left >= right:
      -        return  # サブ配列の長さが1のときに再帰を終了
      -    # 分割段階
      -    mid = left + (right - left) // 2  # 中点を計算
      -    merge_sort(nums, left, mid)  # 左サブ配列を再帰的に処理
      -    merge_sort(nums, mid + 1, right)  # 右サブ配列を再帰的に処理
      -    # マージ段階
      +        return  # 部分配列の長さが 1 になったら再帰を終了
      +    # 分割フェーズ
      +    mid = (left + right) // 2 # 中点を計算
      +    merge_sort(nums, left, mid)  # 左部分配列を再帰処理
      +    merge_sort(nums, mid + 1, right)  # 右部分配列を再帰処理
      +    # マージフェーズ
           merge(nums, left, mid, right)
       
      -
      merge_sort.cpp
      /* 左サブ配列と右サブ配列をマージ */
      +
      merge_sort.cpp
      /* 左部分配列と右部分配列をマージ */
       void merge(vector<int> &nums, int left, int mid, int right) {
      -    // 左サブ配列の区間は[left, mid]、右サブ配列の区間は[mid+1, right]
      -    // マージ結果を保存する一時配列tmpを作成
      +    // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]
      +    // マージ結果を格納する一時配列 tmp を作成
           vector<int> tmp(right - left + 1);
      -    // 左右サブ配列の開始インデックスを初期化
      +    // 左右の部分配列の開始インデックスを初期化する
           int i = left, j = mid + 1, k = 0;
      -    // 両サブ配列に要素がある間、小さい方の要素を一時配列にコピー
      +    // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする
           while (i <= mid && j <= right) {
               if (nums[i] <= nums[j])
                   tmp[k++] = nums[i++];
               else
                   tmp[k++] = nums[j++];
           }
      -    // 左右サブ配列の残りの要素を一時配列にコピー
      +    // 左右の部分配列の残り要素を一時配列にコピーする
           while (i <= mid) {
               tmp[k++] = nums[i++];
           }
           while (j <= right) {
               tmp[k++] = nums[j++];
           }
      -    // 一時配列tmpの要素を元の配列numsの対応する区間にコピー
      +    // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする
           for (k = 0; k < tmp.size(); k++) {
               nums[left + k] = tmp[k];
           }
      @@ -4511,12 +4511,12 @@
       void mergeSort(vector<int> &nums, int left, int right) {
           // 終了条件
           if (left >= right)
      -        return; // サブ配列の長さが1の時、再帰を終了
      -    // 分割段階
      +        return; // 部分配列の長さが 1 になったら再帰を終了
      +    // 分割フェーズ
           int mid = left + (right - left) / 2;    // 中点を計算
      -    mergeSort(nums, left, mid);      // 左サブ配列を再帰的に処理
      -    mergeSort(nums, mid + 1, right); // 右サブ配列を再帰的に処理
      -    // マージ段階
      +    mergeSort(nums, left, mid);      // 左部分配列を再帰処理
      +    mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理
      +    // マージフェーズ
           merge(nums, left, mid, right);
       }
       
      @@ -4524,26 +4524,26 @@
      merge_sort.java
      /* 左部分配列と右部分配列をマージ */
       void merge(int[] nums, int left, int mid, int right) {
      -    // 左部分配列区間は [left, mid]、右部分配列区間は [mid+1, right]
      -    // 一時配列 tmp を作成してマージ結果を格納
      +    // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]
      +    // マージ結果を格納する一時配列 tmp を作成
           int[] tmp = new int[right - left + 1];
      -    // 左右部分配列の開始インデックスを初期化
      +    // 左右の部分配列の開始インデックスを初期化する
           int i = left, j = mid + 1, k = 0;
      -    // 両部分配列にまだ要素がある間、比較してより小さい要素を一時配列にコピー
      +    // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする
           while (i <= mid && j <= right) {
               if (nums[i] <= nums[j])
                   tmp[k++] = nums[i++];
               else
                   tmp[k++] = nums[j++];
           }
      -    // 左右部分配列の残りの要素を一時配列にコピー
      +    // 左右の部分配列の残り要素を一時配列にコピーする
           while (i <= mid) {
               tmp[k++] = nums[i++];
           }
           while (j <= right) {
               tmp[k++] = nums[j++];
           }
      -    // 一時配列 tmp の要素を元の配列 nums の対応する区間にコピーバック
      +    // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする
           for (k = 0; k < tmp.length; k++) {
               nums[left + k] = tmp[k];
           }
      @@ -4553,91 +4553,501 @@
       void mergeSort(int[] nums, int left, int right) {
           // 終了条件
           if (left >= right)
      -        return; // 部分配列の長さが 1 のとき再帰を終了
      -    // 分割段階
      +        return; // 部分配列の長さが 1 になったら再帰を終了
      +    // 分割フェーズ
           int mid = left + (right - left) / 2; // 中点を計算
      -    mergeSort(nums, left, mid); // 左部分配列を再帰的に処理
      -    mergeSort(nums, mid + 1, right); // 右部分配列を再帰的に処理
      -    // マージ段階
      +    mergeSort(nums, left, mid); // 左部分配列を再帰処理
      +    mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理
      +    // マージフェーズ
           merge(nums, left, mid, right);
       }
       
      -
      merge_sort.cs
      [class]{merge_sort}-[func]{Merge}
      -
      -[class]{merge_sort}-[func]{MergeSort}
      +
      merge_sort.cs
      /* 左部分配列と右部分配列をマージ */
      +void Merge(int[] nums, int left, int mid, int right) {
      +    // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]
      +    // マージ結果を格納する一時配列 tmp を作成
      +    int[] tmp = new int[right - left + 1];
      +    // 左右の部分配列の開始インデックスを初期化する
      +    int i = left, j = mid + 1, k = 0;
      +    // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする
      +    while (i <= mid && j <= right) {
      +        if (nums[i] <= nums[j])
      +            tmp[k++] = nums[i++];
      +        else
      +            tmp[k++] = nums[j++];
      +    }
      +    // 左右の部分配列の残り要素を一時配列にコピーする
      +    while (i <= mid) {
      +        tmp[k++] = nums[i++];
      +    }
      +    while (j <= right) {
      +        tmp[k++] = nums[j++];
      +    }
      +    // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする
      +    for (k = 0; k < tmp.Length; ++k) {
      +        nums[left + k] = tmp[k];
      +    }
      +}
      +
      +/* マージソート */
      +void MergeSort(int[] nums, int left, int right) {
      +    // 終了条件
      +    if (left >= right) return;       // 部分配列の長さが 1 になったら再帰を終了
      +    // 分割フェーズ
      +    int mid = left + (right - left) / 2;    // 中点を計算
      +    MergeSort(nums, left, mid);      // 左部分配列を再帰処理
      +    MergeSort(nums, mid + 1, right); // 右部分配列を再帰処理
      +    // マージフェーズ
      +    Merge(nums, left, mid, right);
      +}
       
      -
      merge_sort.go
      [class]{}-[func]{merge}
      -
      -[class]{}-[func]{mergeSort}
      +
      merge_sort.go
      /* 左部分配列と右部分配列をマージ */
      +func merge(nums []int, left, mid, right int) {
      +    // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]
      +    // マージ結果を格納する一時配列 tmp を作成
      +    tmp := make([]int, right-left+1)
      +    // 左右の部分配列の開始インデックスを初期化する
      +    i, j, k := left, mid+1, 0
      +    // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする
      +    for i <= mid && j <= right {
      +        if nums[i] <= nums[j] {
      +            tmp[k] = nums[i]
      +            i++
      +        } else {
      +            tmp[k] = nums[j]
      +            j++
      +        }
      +        k++
      +    }
      +    // 左右の部分配列の残り要素を一時配列にコピーする
      +    for i <= mid {
      +        tmp[k] = nums[i]
      +        i++
      +        k++
      +    }
      +    for j <= right {
      +        tmp[k] = nums[j]
      +        j++
      +        k++
      +    }
      +    // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする
      +    for k := 0; k < len(tmp); k++ {
      +        nums[left+k] = tmp[k]
      +    }
      +}
      +
      +/* マージソート */
      +func mergeSort(nums []int, left, right int) {
      +    // 終了条件
      +    if left >= right {
      +        return
      +    }
      +    // 分割フェーズ
      +    mid := left + (right - left) / 2
      +    mergeSort(nums, left, mid)
      +    mergeSort(nums, mid+1, right)
      +    // マージフェーズ
      +    merge(nums, left, mid, right)
      +}
       
      -
      merge_sort.swift
      [class]{}-[func]{merge}
      -
      -[class]{}-[func]{mergeSort}
      +
      merge_sort.swift
      /* 左部分配列と右部分配列をマージ */
      +func merge(nums: inout [Int], left: Int, mid: Int, right: Int) {
      +    // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]
      +    // マージ結果を格納する一時配列 tmp を作成
      +    var tmp = Array(repeating: 0, count: right - left + 1)
      +    // 左右の部分配列の開始インデックスを初期化する
      +    var i = left, j = mid + 1, k = 0
      +    // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする
      +    while i <= mid, j <= right {
      +        if nums[i] <= nums[j] {
      +            tmp[k] = nums[i]
      +            i += 1
      +        } else {
      +            tmp[k] = nums[j]
      +            j += 1
      +        }
      +        k += 1
      +    }
      +    // 左右の部分配列の残り要素を一時配列にコピーする
      +    while i <= mid {
      +        tmp[k] = nums[i]
      +        i += 1
      +        k += 1
      +    }
      +    while j <= right {
      +        tmp[k] = nums[j]
      +        j += 1
      +        k += 1
      +    }
      +    // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする
      +    for k in tmp.indices {
      +        nums[left + k] = tmp[k]
      +    }
      +}
      +
      +/* マージソート */
      +func mergeSort(nums: inout [Int], left: Int, right: Int) {
      +    // 終了条件
      +    if left >= right { // 部分配列の長さが 1 になったら再帰を終了
      +        return
      +    }
      +    // 分割フェーズ
      +    let mid = left + (right - left) / 2 // 中点を計算
      +    mergeSort(nums: &nums, left: left, right: mid) // 左部分配列を再帰処理
      +    mergeSort(nums: &nums, left: mid + 1, right: right) // 右部分配列を再帰処理
      +    // マージフェーズ
      +    merge(nums: &nums, left: left, mid: mid, right: right)
      +}
       
      -
      merge_sort.js
      [class]{}-[func]{merge}
      -
      -[class]{}-[func]{mergeSort}
      +
      merge_sort.js
      /* 左部分配列と右部分配列をマージ */
      +function merge(nums, left, mid, right) {
      +    // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]
      +    // マージ結果を格納する一時配列 tmp を作成
      +    const tmp = new Array(right - left + 1);
      +    // 左右の部分配列の開始インデックスを初期化する
      +    let i = left,
      +        j = mid + 1,
      +        k = 0;
      +    // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする
      +    while (i <= mid && j <= right) {
      +        if (nums[i] <= nums[j]) {
      +            tmp[k++] = nums[i++];
      +        } else {
      +            tmp[k++] = nums[j++];
      +        }
      +    }
      +    // 左右の部分配列の残り要素を一時配列にコピーする
      +    while (i <= mid) {
      +        tmp[k++] = nums[i++];
      +    }
      +    while (j <= right) {
      +        tmp[k++] = nums[j++];
      +    }
      +    // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする
      +    for (k = 0; k < tmp.length; k++) {
      +        nums[left + k] = tmp[k];
      +    }
      +}
      +
      +/* マージソート */
      +function mergeSort(nums, left, right) {
      +    // 終了条件
      +    if (left >= right) return; // 部分配列の長さが 1 になったら再帰を終了
      +    // 分割フェーズ
      +    let mid = Math.floor(left + (right - left) / 2); // 中点を計算
      +    mergeSort(nums, left, mid); // 左部分配列を再帰処理
      +    mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理
      +    // マージフェーズ
      +    merge(nums, left, mid, right);
      +}
       
      -
      merge_sort.ts
      [class]{}-[func]{merge}
      -
      -[class]{}-[func]{mergeSort}
      +
      merge_sort.ts
      /* 左部分配列と右部分配列をマージ */
      +function merge(nums: number[], left: number, mid: number, right: number): void {
      +    // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]
      +    // マージ結果を格納する一時配列 tmp を作成
      +    const tmp = new Array(right - left + 1);
      +    // 左右の部分配列の開始インデックスを初期化する
      +    let i = left,
      +        j = mid + 1,
      +        k = 0;
      +    // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする
      +    while (i <= mid && j <= right) {
      +        if (nums[i] <= nums[j]) {
      +            tmp[k++] = nums[i++];
      +        } else {
      +            tmp[k++] = nums[j++];
      +        }
      +    }
      +    // 左右の部分配列の残り要素を一時配列にコピーする
      +    while (i <= mid) {
      +        tmp[k++] = nums[i++];
      +    }
      +    while (j <= right) {
      +        tmp[k++] = nums[j++];
      +    }
      +    // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする
      +    for (k = 0; k < tmp.length; k++) {
      +        nums[left + k] = tmp[k];
      +    }
      +}
      +
      +/* マージソート */
      +function mergeSort(nums: number[], left: number, right: number): void {
      +    // 終了条件
      +    if (left >= right) return; // 部分配列の長さが 1 になったら再帰を終了
      +    // 分割フェーズ
      +    let mid = Math.floor(left + (right - left) / 2); // 中点を計算
      +    mergeSort(nums, left, mid); // 左部分配列を再帰処理
      +    mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理
      +    // マージフェーズ
      +    merge(nums, left, mid, right);
      +}
       
      -
      merge_sort.dart
      [class]{}-[func]{merge}
      -
      -[class]{}-[func]{mergeSort}
      +
      merge_sort.dart
      /* 左部分配列と右部分配列をマージ */
      +void merge(List<int> nums, int left, int mid, int right) {
      +  // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]
      +  // マージ結果を格納する一時配列 tmp を作成
      +  List<int> tmp = List.filled(right - left + 1, 0);
      +  // 左右の部分配列の開始インデックスを初期化する
      +  int i = left, j = mid + 1, k = 0;
      +  // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする
      +  while (i <= mid && j <= right) {
      +    if (nums[i] <= nums[j])
      +      tmp[k++] = nums[i++];
      +    else
      +      tmp[k++] = nums[j++];
      +  }
      +  // 左右の部分配列の残り要素を一時配列にコピーする
      +  while (i <= mid) {
      +    tmp[k++] = nums[i++];
      +  }
      +  while (j <= right) {
      +    tmp[k++] = nums[j++];
      +  }
      +  // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする
      +  for (k = 0; k < tmp.length; k++) {
      +    nums[left + k] = tmp[k];
      +  }
      +}
      +
      +/* マージソート */
      +void mergeSort(List<int> nums, int left, int right) {
      +  // 終了条件
      +  if (left >= right) return; // 部分配列の長さが 1 になったら再帰を終了
      +  // 分割フェーズ
      +  int mid = left + (right - left) ~/ 2; // 中点を計算
      +  mergeSort(nums, left, mid); // 左部分配列を再帰処理
      +  mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理
      +  // マージフェーズ
      +  merge(nums, left, mid, right);
      +}
       
      -
      merge_sort.rs
      [class]{}-[func]{merge}
      -
      -[class]{}-[func]{merge_sort}
      +
      merge_sort.rs
      /* 左部分配列と右部分配列をマージ */
      +fn merge(nums: &mut [i32], left: usize, mid: usize, right: usize) {
      +    // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]
      +    // マージ結果を格納する一時配列 tmp を作成
      +    let tmp_size = right - left + 1;
      +    let mut tmp = vec![0; tmp_size];
      +    // 左右の部分配列の開始インデックスを初期化する
      +    let (mut i, mut j, mut k) = (left, mid + 1, 0);
      +    // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする
      +    while i <= mid && j <= right {
      +        if nums[i] <= nums[j] {
      +            tmp[k] = nums[i];
      +            i += 1;
      +        } else {
      +            tmp[k] = nums[j];
      +            j += 1;
      +        }
      +        k += 1;
      +    }
      +    // 左右の部分配列の残り要素を一時配列にコピーする
      +    while i <= mid {
      +        tmp[k] = nums[i];
      +        k += 1;
      +        i += 1;
      +    }
      +    while j <= right {
      +        tmp[k] = nums[j];
      +        k += 1;
      +        j += 1;
      +    }
      +    // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする
      +    for k in 0..tmp_size {
      +        nums[left + k] = tmp[k];
      +    }
      +}
      +
      +/* マージソート */
      +fn merge_sort(nums: &mut [i32], left: usize, right: usize) {
      +    // 終了条件
      +    if left >= right {
      +        return; // 部分配列の長さが 1 になったら再帰を終了
      +    }
      +
      +    // 分割フェーズ
      +    let mid = left + (right - left) / 2; // 中点を計算
      +    merge_sort(nums, left, mid); // 左部分配列を再帰処理
      +    merge_sort(nums, mid + 1, right); // 右部分配列を再帰処理
      +
      +    // マージフェーズ
      +    merge(nums, left, mid, right);
      +}
       
      -
      merge_sort.c
      [class]{}-[func]{merge}
      -
      -[class]{}-[func]{mergeSort}
      +
      merge_sort.c
      /* 左部分配列と右部分配列をマージ */
      +void merge(int *nums, int left, int mid, int right) {
      +    // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]
      +    // マージ結果を格納する一時配列 tmp を作成
      +    int tmpSize = right - left + 1;
      +    int *tmp = (int *)malloc(tmpSize * sizeof(int));
      +    // 左右の部分配列の開始インデックスを初期化する
      +    int i = left, j = mid + 1, k = 0;
      +    // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする
      +    while (i <= mid && j <= right) {
      +        if (nums[i] <= nums[j]) {
      +            tmp[k++] = nums[i++];
      +        } else {
      +            tmp[k++] = nums[j++];
      +        }
      +    }
      +    // 左右の部分配列の残り要素を一時配列にコピーする
      +    while (i <= mid) {
      +        tmp[k++] = nums[i++];
      +    }
      +    while (j <= right) {
      +        tmp[k++] = nums[j++];
      +    }
      +    // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする
      +    for (k = 0; k < tmpSize; ++k) {
      +        nums[left + k] = tmp[k];
      +    }
      +    // メモリを解放する
      +    free(tmp);
      +}
      +
      +/* マージソート */
      +void mergeSort(int *nums, int left, int right) {
      +    // 終了条件
      +    if (left >= right)
      +        return; // 部分配列の長さが 1 になったら再帰を終了
      +    // 分割フェーズ
      +    int mid = left + (right - left) / 2;    // 中点を計算
      +    mergeSort(nums, left, mid);      // 左部分配列を再帰処理
      +    mergeSort(nums, mid + 1, right); // 右部分配列を再帰処理
      +    // マージフェーズ
      +    merge(nums, left, mid, right);
      +}
       
      -
      merge_sort.kt
      [class]{}-[func]{merge}
      -
      -[class]{}-[func]{mergeSort}
      +
      merge_sort.kt
      /* 左部分配列と右部分配列をマージ */
      +fun merge(nums: IntArray, left: Int, mid: Int, right: Int) {
      +    // 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]
      +    // マージ結果を格納する一時配列 tmp を作成
      +    val tmp = IntArray(right - left + 1)
      +    // 左右の部分配列の開始インデックスを初期化する
      +    var i = left
      +    var j = mid + 1
      +    var k = 0
      +    // 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする
      +    while (i <= mid && j <= right) {
      +        if (nums[i] <= nums[j])
      +            tmp[k++] = nums[i++]
      +        else
      +            tmp[k++] = nums[j++]
      +    }
      +    // 左右の部分配列の残り要素を一時配列にコピーする
      +    while (i <= mid) {
      +        tmp[k++] = nums[i++]
      +    }
      +    while (j <= right) {
      +        tmp[k++] = nums[j++]
      +    }
      +    // 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする
      +    for (l in tmp.indices) {
      +        nums[left + l] = tmp[l]
      +    }
      +}
      +
      +/* マージソート */
      +fun mergeSort(nums: IntArray, left: Int, right: Int) {
      +    // 終了条件
      +    if (left >= right) return  // 部分配列の長さが 1 になったら再帰を終了
      +    // 分割フェーズ
      +    val mid = left + (right - left) / 2 // 中点を計算
      +    mergeSort(nums, left, mid) // 左部分配列を再帰処理
      +    mergeSort(nums, mid + 1, right) // 右部分配列を再帰処理
      +    // マージフェーズ
      +    merge(nums, left, mid, right)
      +}
       
      -
      merge_sort.rb
      [class]{}-[func]{merge}
      -
      -[class]{}-[func]{merge_sort}
      +
      merge_sort.rb
      ### 左部分配列と右部分配列をマージ ###
      +def merge(nums, left, mid, right)
      +  # 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]
      +  # マージ結果を格納する一時配列 tmp を作成
      +  tmp = Array.new(right - left + 1, 0)
      +  # 左右の部分配列の開始インデックスを初期化する
      +  i, j, k = left, mid + 1, 0
      +  # 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする
      +  while i <= mid && j <= right
      +    if nums[i] <= nums[j]
      +      tmp[k] = nums[i]
      +      i += 1
      +    else
      +      tmp[k] = nums[j]
      +      j += 1
      +    end
      +    k += 1
      +  end
      +  # 左右の部分配列の残り要素を一時配列にコピーする
      +  while i <= mid
      +    tmp[k] = nums[i]
      +    i += 1
      +    k += 1
      +  end
      +  while j <= right
      +    tmp[k] = nums[j]
      +    j += 1
      +    k += 1
      +  end
      +  # 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする
      +  (0...tmp.length).each do |k|
      +    nums[left + k] = tmp[k]
      +  end
      +end
      +
      +### マージソート ###
      +def merge_sort(nums, left, right)
      +  # 終了条件
      +  # 部分配列の長さが 1 になったら再帰を終了する
      +  return if left >= right
      +  # 分割フェーズ
      +  mid = left + (right - left) / 2 # 中点を計算
      +  merge_sort(nums, left, mid) # 左部分配列を再帰処理
      +  merge_sort(nums, mid + 1, right) # 右部分配列を再帰処理
      +  # マージフェーズ
      +  merge(nums, left, mid, right)
      +end
       
      +
      +コードの可視化 +

      +

      +

      11.6.2   アルゴリズムの特性

        -
      • \(O(n \log n)\)の時間計算量、非適応ソート:分割により高さ\(\log n\)の再帰ツリーが作成され、各層で合計\(n\)回の操作をマージし、全体的な時間計算量は\(O(n \log n)\)となります。
      • -
      • \(O(n)\)の空間計算量、非インプレースソート:再帰深度は\(\log n\)で、\(O(\log n)\)のスタックフレーム空間を使用します。マージ操作には補助配列が必要で、追加の\(O(n)\)空間を使用します。
      • -
      • 安定ソート:マージプロセス中、等しい要素の順序は変更されません。
      • +
      • 時間計算量は \(O(n \log n)\)、非適応型ソート:分割によって高さ \(\log n\) の再帰木が生成され、各層でのマージ操作の総数は \(n\) であるため、全体の時間計算量は \(O(n \log n)\) です。
      • +
      • 空間計算量は \(O(n)\)、インプレースではないソート:再帰の深さは \(\log n\) であり、サイズ \(O(\log n)\) のスタックフレーム領域を使用します。マージ操作は補助配列を用いて実装する必要があり、サイズ \(O(n)\) の追加領域を使用します。
      • +
      • 安定ソート:マージの過程では、等しい要素の順序は変化しません。

      11.6.3   連結リストのソート

      -

      連結リストの場合、マージソートは他のソートアルゴリズムよりも大きな利点があります。連結リストソートタスクの空間計算量を\(O(1)\)に最適化できます

      +

      連結リストに対しては、マージソートは他のソートアルゴリズムと比べて顕著な利点があり、連結リストのソート問題の空間計算量を \(O(1)\) まで最適化できます

        -
      • 分割フェーズ:「再帰」の代わりに「反復」を使用して連結リスト分割作業を実行できるため、再帰で使用されるスタックフレーム空間を節約できます。
      • -
      • マージフェーズ:連結リストでは、ノードの挿入と削除操作は参照(ポインタ)を変更することで実現できるため、マージフェーズ(2つの短い順序付きリストを1つの長い順序付きリストに結合)中に追加のリストを作成する必要がありません。
      • +
      • 分割段階:連結リストの分割は「再帰」の代わりに「反復」で実装できるため、再帰で使用するスタックフレーム領域を省けます。
      • +
      • マージ段階:連結リストでは、ノードの追加や削除は参照(ポインタ)を変更するだけで実現できるため、マージ段階(2 つの短いソート済み連結リストを 1 つの長いソート済み連結リストにマージすること)では追加の連結リストを作成する必要がありません。
      -

      実装の詳細は比較的複雑で、興味のある読者は関連資料を参照して学習してください。

      +

      具体的な実装の詳細は比較的複雑なので、興味のある読者は関連資料を参照して学習してください。

      diff --git a/ja/chapter_sorting/quick_sort/index.html b/ja/chapter_sorting/quick_sort/index.html index e9317a25f..2e9959c38 100644 --- a/ja/chapter_sorting/quick_sort/index.html +++ b/ja/chapter_sorting/quick_sort/index.html @@ -6,7 +6,7 @@ - + @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1591,7 +1591,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1613,7 +1613,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1635,7 +1635,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1942,7 +1942,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2105,7 +2105,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2491,7 +2491,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2547,7 +2547,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2575,7 +2575,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -2895,7 +2895,7 @@ - 11.5.1   アルゴリズムプロセス + 11.5.1   アルゴリズムの流れ @@ -2906,7 +2906,7 @@ - 11.5.2   アルゴリズムの特徴 + 11.5.2   アルゴリズムの特性 @@ -2917,7 +2917,7 @@ - 11.5.3   なぜクイックソートは高速なのか + 11.5.3   クイックソートが速い理由 @@ -2928,7 +2928,7 @@ - 11.5.4   ピボット最適化 + 11.5.4   基準数の最適化 @@ -2939,7 +2939,7 @@ - 11.5.5   末尾再帰最適化 + 11.5.5   再帰の深さの最適化 @@ -3218,7 +3218,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3274,7 +3274,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3302,7 +3302,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3495,7 +3495,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3523,7 +3523,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3664,7 +3664,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3692,7 +3692,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3720,7 +3720,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3748,7 +3748,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3941,7 +3941,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4186,7 +4186,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4332,7 +4332,7 @@ - 11.5.1   アルゴリズムプロセス + 11.5.1   アルゴリズムの流れ @@ -4343,7 +4343,7 @@ - 11.5.2   アルゴリズムの特徴 + 11.5.2   アルゴリズムの特性 @@ -4354,7 +4354,7 @@ - 11.5.3   なぜクイックソートは高速なのか + 11.5.3   クイックソートが速い理由 @@ -4365,7 +4365,7 @@ - 11.5.4   ピボット最適化 + 11.5.4   基準数の最適化 @@ -4376,7 +4376,7 @@ - 11.5.5   末尾再帰最適化 + 11.5.5   再帰の深さの最適化 @@ -4421,17 +4421,17 @@

      11.5   クイックソート

      -

      クイックソートは分割統治戦略に基づくソートアルゴリズムで、その効率性と幅広い応用で知られています。

      -

      クイックソートのコア操作は「ピボット分割」で、配列から要素を「ピボット」として選択し、ピボットより小さいすべての要素をその左側に移動し、ピボットより大きいすべての要素をその右側に移動することを目的としています。具体的に、ピボット分割のプロセスは下図に示されます。

      +

      クイックソート(quick sort)は分割統治戦略に基づくソートアルゴリズムであり、実行効率が高く、広く利用されています。

      +

      クイックソートの中核操作は「パーティション」であり、その目的は、配列内のある要素を「基準数」として選び、基準数より小さいすべての要素を左側へ、大きい要素を右側へ移動することです。具体的には、パーティションの流れを下図に示します。

        -
      1. 配列の最も左の要素をピボットとして選択し、2つのポインタijを初期化して配列の両端をそれぞれ指すようにします。
      2. -
      3. 各ラウンドでij)を使用してピボットより大きい(小さい)最初の要素を探索し、次にこれら2つの要素を交換するループを設定します。
      4. -
      5. ijが出会うまでステップ2.を繰り返し、最後にピボットを2つのサブ配列の境界に交換します。
      6. +
      7. 配列の最左端の要素を基準数として選び、2 つのポインタ ij を初期化して、それぞれ配列の両端を指すようにします。
      8. +
      9. ループを設定し、各ラウンドで ij)を使ってそれぞれ基準数より大きい(小さい)最初の要素を探し、その後この 2 つの要素を交換します。
      10. +
      11. ij が出会うまでステップ 2. を繰り返し、最後に基準数を 2 つの部分配列の境界へ交換します。
      -

      Pivot division process

      +

      パーティションの手順

      pivot_division_step2

      @@ -4459,148 +4459,336 @@
      -

      図 11-8   Pivot division process

      +

      図 11-8   パーティションの手順

      -

      ピボット分割後、元の配列は3つの部分に分割されます:左サブ配列、ピボット、右サブ配列で、「左サブ配列の任意の要素 \(\leq\) ピボット \(\leq\) 右サブ配列の任意の要素」を満たします。したがって、これら2つのサブ配列のみをソートすればよいのです。

      +

      パーティションが完了すると、元の配列は左部分配列、基準数、右部分配列の 3 つに分けられ、「左部分配列の任意の要素 \(\leq\) 基準数 \(\leq\) 右部分配列の任意の要素」を満たします。したがって、次はこの 2 つの部分配列だけをソートすれば済みます。

      クイックソートの分割統治戦略

      -

      ピボット分割の本質は、より長い配列のソート問題をより短い2つの配列に簡素化することです。

      +

      パーティションの本質は、長い配列のソート問題を 2 つの短い配列のソート問題へ簡略化することです。

      quick_sort.py
      def partition(self, nums: list[int], left: int, right: int) -> int:
      -    """分割"""
      -    # nums[left] をピボットとして使用
      +    """番兵分割"""
      +    # nums[left] を基準値とする
           i, j = left, right
           while i < j:
               while i < j and nums[j] >= nums[left]:
      -            j -= 1  # 右から左へピボットより小さい最初の要素を探す
      +            j -= 1  # 右から左へ基準値未満の最初の要素を探す
               while i < j and nums[i] <= nums[left]:
      -            i += 1  # 左から右へピボットより大きい最初の要素を探す
      -        # 要素を交換
      +            i += 1  # 左から右へ基準値より大きい最初の要素を探す
      +        # 要素の交換
               nums[i], nums[j] = nums[j], nums[i]
      -    # ピボットを2つのサブ配列の境界に交換
      +    # 基準値を 2 つの部分配列の境界へ交換する
           nums[i], nums[left] = nums[left], nums[i]
      -    return i  # ピボットのインデックスを返す
      +    return i  # 基準値のインデックスを返す
       
      -
      quick_sort.cpp
      /* 分割 */
      +
      quick_sort.cpp
      /* 番兵分割 */
       int partition(vector<int> &nums, int left, int right) {
      -    // nums[left]をピボットとして使用
      +    // nums[left] を基準値とする
           int i = left, j = right;
           while (i < j) {
               while (i < j && nums[j] >= nums[left])
      -            j--; // 右から左へピボットより小さい最初の要素を検索
      +            j--;                // 右から左へ基準値未満の最初の要素を探す
               while (i < j && nums[i] <= nums[left])
      -            i++;          // 左から右へピボットより大きい最初の要素を検索
      -        swap(nums, i, j); // これら二つの要素を交換
      +            i++;                // 左から右へ基準値より大きい最初の要素を探す
      +        swap(nums[i], nums[j]); // この 2 つの要素を交換
           }
      -    swap(nums, i, left); // ピボットを二つのサブ配列の境界に交換
      -    return i;            // ピボットのインデックスを返す
      +    swap(nums[i], nums[left]);  // 基準値を 2 つの部分配列の境界へ交換する
      +    return i;                   // 基準値のインデックスを返す
       }
       
      -
      quick_sort.java
      /* 要素を交換 */
      +
      quick_sort.java
      /* 要素の交換 */
       void swap(int[] nums, int i, int j) {
           int tmp = nums[i];
           nums[i] = nums[j];
           nums[j] = tmp;
       }
       
      -/* 分割 */
      +/* 番兵分割 */
       int partition(int[] nums, int left, int right) {
      -    // nums[left] を基準値として使用
      +    // nums[left] を基準値とする
           int i = left, j = right;
           while (i < j) {
               while (i < j && nums[j] >= nums[left])
      -            j--;          // 右から左へ、基準値より小さい最初の要素を検索
      +            j--;          // 右から左へ基準値未満の最初の要素を探す
               while (i < j && nums[i] <= nums[left])
      -            i++;          // 左から右へ、基準値より大きい最初の要素を検索
      -        swap(nums, i, j); // これら2つの要素を交換
      +            i++;          // 左から右へ基準値より大きい最初の要素を探す
      +        swap(nums, i, j); // この 2 つの要素を交換
           }
      -    swap(nums, i, left);  // 基準値を2つの部分配列の境界に交換
      +    swap(nums, i, left);  // 基準値を 2 つの部分配列の境界へ交換する
           return i;             // 基準値のインデックスを返す
       }
       
      -
      quick_sort.cs
      [class]{quickSort}-[func]{Swap}
      -
      -[class]{quickSort}-[func]{Partition}
      +
      quick_sort.cs
      /* 要素の交換 */
      +void Swap(int[] nums, int i, int j) {
      +    (nums[j], nums[i]) = (nums[i], nums[j]);
      +}
      +
      +/* 番兵分割 */
      +int Partition(int[] nums, int left, int right) {
      +    // nums[left] を基準値とする
      +    int i = left, j = right;
      +    while (i < j) {
      +        while (i < j && nums[j] >= nums[left])
      +            j--;          // 右から左へ基準値未満の最初の要素を探す
      +        while (i < j && nums[i] <= nums[left])
      +            i++;          // 左から右へ基準値より大きい最初の要素を探す
      +        Swap(nums, i, j); // この 2 つの要素を交換
      +    }
      +    Swap(nums, i, left);  // 基準値を 2 つの部分配列の境界へ交換する
      +    return i;             // 基準値のインデックスを返す
      +}
       
      -
      quick_sort.go
      [class]{quickSort}-[func]{partition}
      +
      quick_sort.go
      /* 番兵分割 */
      +func (q *quickSort) partition(nums []int, left, right int) int {
      +    // nums[left] を基準値とする
      +    i, j := left, right
      +    for i < j {
      +        for i < j && nums[j] >= nums[left] {
      +            j-- // 右から左へ基準値未満の最初の要素を探す
      +        }
      +        for i < j && nums[i] <= nums[left] {
      +            i++ // 左から右へ基準値より大きい最初の要素を探す
      +        }
      +        // 要素の交換
      +        nums[i], nums[j] = nums[j], nums[i]
      +    }
      +    // 基準値を 2 つの部分配列の境界へ交換する
      +    nums[i], nums[left] = nums[left], nums[i]
      +    return i // 基準値のインデックスを返す
      +}
       
      -
      quick_sort.swift
      [class]{}-[func]{partition}
      +
      quick_sort.swift
      /* 番兵分割 */
      +func partition(nums: inout [Int], left: Int, right: Int) -> Int {
      +    // nums[left] を基準値とする
      +    var i = left
      +    var j = right
      +    while i < j {
      +        while i < j, nums[j] >= nums[left] {
      +            j -= 1 // 右から左へ基準値未満の最初の要素を探す
      +        }
      +        while i < j, nums[i] <= nums[left] {
      +            i += 1 // 左から右へ基準値より大きい最初の要素を探す
      +        }
      +        nums.swapAt(i, j) // この 2 つの要素を交換
      +    }
      +    nums.swapAt(i, left) // 基準値を 2 つの部分配列の境界へ交換する
      +    return i // 基準値のインデックスを返す
      +}
       
      -
      quick_sort.js
      [class]{QuickSort}-[func]{swap}
      -
      -[class]{QuickSort}-[func]{partition}
      +
      quick_sort.js
      /* 要素の交換 */
      +swap(nums, i, j) {
      +    let tmp = nums[i];
      +    nums[i] = nums[j];
      +    nums[j] = tmp;
      +}
      +
      +/* 番兵分割 */
      +partition(nums, left, right) {
      +    // nums[left] を基準値とする
      +    let i = left,
      +        j = right;
      +    while (i < j) {
      +        while (i < j && nums[j] >= nums[left]) {
      +            j -= 1; // 右から左へ基準値未満の最初の要素を探す
      +        }
      +        while (i < j && nums[i] <= nums[left]) {
      +            i += 1; // 左から右へ基準値より大きい最初の要素を探す
      +        }
      +        // 要素の交換
      +        this.swap(nums, i, j); // この 2 つの要素を交換
      +    }
      +    this.swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する
      +    return i; // 基準値のインデックスを返す
      +}
       
      -
      quick_sort.ts
      [class]{QuickSort}-[func]{swap}
      -
      -[class]{QuickSort}-[func]{partition}
      +
      quick_sort.ts
      /* 要素の交換 */
      +swap(nums: number[], i: number, j: number): void {
      +    let tmp = nums[i];
      +    nums[i] = nums[j];
      +    nums[j] = tmp;
      +}
      +
      +/* 番兵分割 */
      +partition(nums: number[], left: number, right: number): number {
      +    // nums[left] を基準値とする
      +    let i = left,
      +        j = right;
      +    while (i < j) {
      +        while (i < j && nums[j] >= nums[left]) {
      +            j -= 1; // 右から左へ基準値未満の最初の要素を探す
      +        }
      +        while (i < j && nums[i] <= nums[left]) {
      +            i += 1; // 左から右へ基準値より大きい最初の要素を探す
      +        }
      +        // 要素の交換
      +        this.swap(nums, i, j); // この 2 つの要素を交換
      +    }
      +    this.swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する
      +    return i; // 基準値のインデックスを返す
      +}
       
      -
      quick_sort.dart
      [class]{QuickSort}-[func]{_swap}
      -
      -[class]{QuickSort}-[func]{_partition}
      +
      quick_sort.dart
      /* 要素の交換 */
      +void _swap(List<int> nums, int i, int j) {
      +  int tmp = nums[i];
      +  nums[i] = nums[j];
      +  nums[j] = tmp;
      +}
      +
      +/* 番兵分割 */
      +int _partition(List<int> nums, int left, int right) {
      +  // nums[left] を基準値とする
      +  int i = left, j = right;
      +  while (i < j) {
      +    while (i < j && nums[j] >= nums[left]) j--; // 右から左へ基準値未満の最初の要素を探す
      +    while (i < j && nums[i] <= nums[left]) i++; // 左から右へ基準値より大きい最初の要素を探す
      +    _swap(nums, i, j); // この 2 つの要素を交換
      +  }
      +  _swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する
      +  return i; // 基準値のインデックスを返す
      +}
       
      -
      quick_sort.rs
      [class]{QuickSort}-[func]{partition}
      +
      quick_sort.rs
      /* 番兵分割 */
      +fn partition(nums: &mut [i32], left: usize, right: usize) -> usize {
      +    // nums[left] を基準値とする
      +    let (mut i, mut j) = (left, right);
      +    while i < j {
      +        while i < j && nums[j] >= nums[left] {
      +            j -= 1; // 右から左へ基準値未満の最初の要素を探す
      +        }
      +        while i < j && nums[i] <= nums[left] {
      +            i += 1; // 左から右へ基準値より大きい最初の要素を探す
      +        }
      +        nums.swap(i, j); // この 2 つの要素を交換
      +    }
      +    nums.swap(i, left); // 基準値を 2 つの部分配列の境界へ交換する
      +    i // 基準値のインデックスを返す
      +}
       
      -
      quick_sort.c
      [class]{}-[func]{swap}
      -
      -[class]{}-[func]{partition}
      +
      quick_sort.c
      /* 要素の交換 */
      +void swap(int nums[], int i, int j) {
      +    int tmp = nums[i];
      +    nums[i] = nums[j];
      +    nums[j] = tmp;
      +}
      +
      +/* 番兵分割 */
      +int partition(int nums[], int left, int right) {
      +    // nums[left] を基準値とする
      +    int i = left, j = right;
      +    while (i < j) {
      +        while (i < j && nums[j] >= nums[left]) {
      +            j--; // 右から左へ基準値未満の最初の要素を探す
      +        }
      +        while (i < j && nums[i] <= nums[left]) {
      +            i++; // 左から右へ基準値より大きい最初の要素を探す
      +        }
      +        // この 2 つの要素を交換
      +        swap(nums, i, j);
      +    }
      +    // 基準値を 2 つの部分配列の境界へ交換する
      +    swap(nums, i, left);
      +    // 基準値のインデックスを返す
      +    return i;
      +}
       
      -
      quick_sort.kt
      [class]{}-[func]{swap}
      -
      -[class]{}-[func]{partition}
      +
      quick_sort.kt
      /* 要素の交換 */
      +fun swap(nums: IntArray, i: Int, j: Int) {
      +    val temp = nums[i]
      +    nums[i] = nums[j]
      +    nums[j] = temp
      +}
      +
      +/* 番兵分割 */
      +fun partition(nums: IntArray, left: Int, right: Int): Int {
      +    // nums[left] を基準値とする
      +    var i = left
      +    var j = right
      +    while (i < j) {
      +        while (i < j && nums[j] >= nums[left])
      +            j--           // 右から左へ基準値未満の最初の要素を探す
      +        while (i < j && nums[i] <= nums[left])
      +            i++           // 左から右へ基準値より大きい最初の要素を探す
      +        swap(nums, i, j)  // この 2 つの要素を交換
      +    }
      +    swap(nums, i, left)   // 基準値を 2 つの部分配列の境界へ交換する
      +    return i              // 基準値のインデックスを返す
      +}
       
      -
      quick_sort.rb
      [class]{QuickSort}-[func]{partition}
      +
      quick_sort.rb
      ### 番兵分割 ###
      +def partition(nums, left, right)
      +  # nums[left] を基準値とする
      +  i, j = left, right
      +  while i < j
      +    while i < j && nums[j] >= nums[left]
      +      j -= 1 # 右から左へ基準値未満の最初の要素を探す
      +    end
      +    while i < j && nums[i] <= nums[left]
      +      i += 1 # 左から右へ基準値より大きい最初の要素を探す
      +    end
      +    # 要素の交換
      +    nums[i], nums[j] = nums[j], nums[i]
      +  end
      +  # 基準値を 2 つの部分配列の境界へ交換する
      +  nums[i], nums[left] = nums[left], nums[i]
      +  i # 基準値のインデックスを返す
      +end
       
      -

      11.5.1   アルゴリズムプロセス

      -

      クイックソートの全体的なプロセスは下図に示されます。

      +
      +コードの可視化 +

      +

      +
      +

      11.5.1   アルゴリズムの流れ

      +

      クイックソート全体の流れを下図に示します。

        -
      1. まず、元の配列に対して「ピボット分割」を実行し、未ソートの左と右のサブ配列を取得します。
      2. -
      3. 次に、左と右のサブ配列に対してそれぞれ再帰的に「ピボット分割」を実行します。
      4. -
      5. サブ配列の長さが1になるまで再帰を続け、配列全体のソートを完了します。
      6. +
      7. まず、元の配列に対して 1 回「パーティション」を実行し、未ソートの左部分配列と右部分配列を得ます。
      8. +
      9. 次に、左部分配列と右部分配列に対してそれぞれ再帰的に「パーティション」を実行します。
      10. +
      11. 部分配列の長さが 1 になるまで再帰を続け、配列全体のソートを完了します。
      -

      Quick sort process

      -

      図 11-9   Quick sort process

      +

      クイックソートの流れ

      +

      図 11-9   クイックソートの流れ

      quick_sort.py
      def quick_sort(self, nums: list[int], left: int, right: int):
           """クイックソート"""
      -    # サブ配列の長さが1のときに再帰を終了
      +    # 部分配列の長さが 1 なら再帰を終了する
           if left >= right:
               return
      -    # 分割
      +    # 番兵分割
           pivot = self.partition(nums, left, right)
      -    # 左サブ配列と右サブ配列を再帰的に処理
      +    # 左右の部分配列を再帰処理
           self.quick_sort(nums, left, pivot - 1)
           self.quick_sort(nums, pivot + 1, right)
       
      @@ -4608,12 +4796,12 @@
      quick_sort.cpp
      /* クイックソート */
       void quickSort(vector<int> &nums, int left, int right) {
      -    // サブ配列の長さが1の時、再帰を終了
      +    // 部分配列の長さが 1 なら再帰を終了する
           if (left >= right)
               return;
      -    // 分割
      +    // 番兵分割
           int pivot = partition(nums, left, right);
      -    // 左サブ配列と右サブ配列を再帰的に処理
      +    // 左右の部分配列を再帰処理
           quickSort(nums, left, pivot - 1);
           quickSort(nums, pivot + 1, right);
       }
      @@ -4622,83 +4810,191 @@
       
      quick_sort.java
      /* クイックソート */
       void quickSort(int[] nums, int left, int right) {
      -    // 部分配列の長さが 1 のとき再帰を終了
      +    // 部分配列の長さが 1 なら再帰を終了する
           if (left >= right)
               return;
      -    // 分割
      +    // 番兵分割
           int pivot = partition(nums, left, right);
      -    // 左部分配列と右部分配列を再帰的に処理
      +    // 左右の部分配列を再帰処理
           quickSort(nums, left, pivot - 1);
           quickSort(nums, pivot + 1, right);
       }
       
      -
      quick_sort.cs
      [class]{quickSort}-[func]{QuickSort}
      +
      quick_sort.cs
      /* クイックソート */
      +void QuickSort(int[] nums, int left, int right) {
      +    // 部分配列の長さが 1 なら再帰を終了する
      +    if (left >= right)
      +        return;
      +    // 番兵分割
      +    int pivot = Partition(nums, left, right);
      +    // 左右の部分配列を再帰処理
      +    QuickSort(nums, left, pivot - 1);
      +    QuickSort(nums, pivot + 1, right);
      +}
       
      -
      quick_sort.go
      [class]{quickSort}-[func]{quickSort}
      +
      quick_sort.go
      /* クイックソート */
      +func (q *quickSort) quickSort(nums []int, left, right int) {
      +    // 部分配列の長さが 1 なら再帰を終了する
      +    if left >= right {
      +        return
      +    }
      +    // 番兵分割
      +    pivot := q.partition(nums, left, right)
      +    // 左右の部分配列を再帰処理
      +    q.quickSort(nums, left, pivot-1)
      +    q.quickSort(nums, pivot+1, right)
      +}
       
      -
      quick_sort.swift
      [class]{}-[func]{quickSort}
      +
      quick_sort.swift
      /* クイックソート */
      +func quickSort(nums: inout [Int], left: Int, right: Int) {
      +    // 部分配列の長さが 1 なら再帰を終了する
      +    if left >= right {
      +        return
      +    }
      +    // 番兵分割
      +    let pivot = partition(nums: &nums, left: left, right: right)
      +    // 左右の部分配列を再帰処理
      +    quickSort(nums: &nums, left: left, right: pivot - 1)
      +    quickSort(nums: &nums, left: pivot + 1, right: right)
      +}
       
      -
      quick_sort.js
      [class]{QuickSort}-[func]{quickSort}
      +
      quick_sort.js
      /* クイックソート */
      +quickSort(nums, left, right) {
      +    // 部分配列の長さが 1 なら再帰を終了する
      +    if (left >= right) return;
      +    // 番兵分割
      +    const pivot = this.partition(nums, left, right);
      +    // 左右の部分配列を再帰処理
      +    this.quickSort(nums, left, pivot - 1);
      +    this.quickSort(nums, pivot + 1, right);
      +}
       
      -
      quick_sort.ts
      [class]{QuickSort}-[func]{quickSort}
      +
      quick_sort.ts
      /* クイックソート */
      +quickSort(nums: number[], left: number, right: number): void {
      +    // 部分配列の長さが 1 なら再帰を終了する
      +    if (left >= right) {
      +        return;
      +    }
      +    // 番兵分割
      +    const pivot = this.partition(nums, left, right);
      +    // 左右の部分配列を再帰処理
      +    this.quickSort(nums, left, pivot - 1);
      +    this.quickSort(nums, pivot + 1, right);
      +}
       
      -
      quick_sort.dart
      [class]{QuickSort}-[func]{quickSort}
      +
      quick_sort.dart
      /* クイックソート */
      +void quickSort(List<int> nums, int left, int right) {
      +  // 部分配列の長さが 1 なら再帰を終了する
      +  if (left >= right) return;
      +  // 番兵分割
      +  int pivot = _partition(nums, left, right);
      +  // 左右の部分配列を再帰処理
      +  quickSort(nums, left, pivot - 1);
      +  quickSort(nums, pivot + 1, right);
      +}
       
      -
      quick_sort.rs
      [class]{QuickSort}-[func]{quick_sort}
      +
      quick_sort.rs
      /* クイックソート */
      +pub fn quick_sort(left: i32, right: i32, nums: &mut [i32]) {
      +    // 部分配列の長さが 1 なら再帰を終了する
      +    if left >= right {
      +        return;
      +    }
      +    // 番兵分割
      +    let pivot = Self::partition(nums, left as usize, right as usize) as i32;
      +    // 左右の部分配列を再帰処理
      +    Self::quick_sort(left, pivot - 1, nums);
      +    Self::quick_sort(pivot + 1, right, nums);
      +}
       
      -
      quick_sort.c
      [class]{}-[func]{quickSort}
      +
      quick_sort.c
      /* クイックソート */
      +void quickSort(int nums[], int left, int right) {
      +    // 部分配列の長さが 1 なら再帰を終了する
      +    if (left >= right) {
      +        return;
      +    }
      +    // 番兵分割
      +    int pivot = partition(nums, left, right);
      +    // 左右の部分配列を再帰処理
      +    quickSort(nums, left, pivot - 1);
      +    quickSort(nums, pivot + 1, right);
      +}
       
      -
      quick_sort.kt
      [class]{}-[func]{quickSort}
      +
      quick_sort.kt
      /* クイックソート */
      +fun quickSort(nums: IntArray, left: Int, right: Int) {
      +    // 部分配列の長さが 1 なら再帰を終了する
      +    if (left >= right) return
      +    // 番兵分割
      +    val pivot = partition(nums, left, right)
      +    // 左右の部分配列を再帰処理
      +    quickSort(nums, left, pivot - 1)
      +    quickSort(nums, pivot + 1, right)
      +}
       
      -
      quick_sort.rb
      [class]{QuickSort}-[func]{quick_sort}
      +
      quick_sort.rb
      ### クイックソートクラス ###
      +def quick_sort(nums, left, right)
      +  # 部分配列の長さが 1 でない場合は再帰する
      +  if left < right
      +    # 番兵分割
      +    pivot = partition(nums, left, right)
      +    # 左右の部分配列を再帰処理
      +    quick_sort(nums, left, pivot - 1)
      +    quick_sort(nums, pivot + 1, right)
      +  end
      +  nums
      +end
       
      -

      11.5.2   アルゴリズムの特徴

      +
      +コードの可視化 +

      +

      +
      +

      11.5.2   アルゴリズムの特性

        -
      • \(O(n \log n)\)の時間計算量、非適応ソート:平均的なケースでは、ピボット分割の再帰レベルは\(\log n\)で、レベルあたりのループの総数は\(n\)であり、全体で\(O(n \log n)\)の時間を使用します。最悪の場合、各ラウンドのピボット分割は長さ\(n\)の配列を長さ\(0\)\(n - 1\)の2つのサブ配列に分割し、再帰レベル数が\(n\)に達すると、各レベルのループ数は\(n\)で、使用される総時間は\(O(n^2)\)です。
      • -
      • \(O(n)\)の空間計算量、インプレースソート:入力配列が完全に逆順の場合、最悪の再帰深度は\(n\)に達し、\(O(n)\)のスタックフレーム空間を使用します。ソート操作は追加の配列の助けなしに元の配列で実行されます。
      • -
      • 非安定ソート:ピボット分割の最終ステップで、ピボットは等しい要素の右側に交換される可能性があります。
      • +
      • 時間計算量は \(O(n \log n)\)、非適応型ソート:平均的な場合、パーティションの再帰の深さは \(\log n\) で、各層の総ループ回数は \(n\) のため、全体で \(O(n \log n)\) 時間を要します。最悪の場合、各回のパーティション操作で長さ \(n\) の配列が長さ \(0\)\(n - 1\) の 2 つの部分配列に分割され、このとき再帰の深さは \(n\) に達し、各層のループ回数は \(n\) となるため、全体で \(O(n^2)\) 時間を要します。
      • +
      • 空間計算量は \(O(n)\)、インプレースソート:入力配列が完全な逆順の場合、最悪の再帰深さ \(n\) に達し、\(O(n)\) のスタックフレーム空間を使用します。ソート操作は元の配列上で行われ、追加の配列は用いません。
      • +
      • 非安定ソート:パーティションの最後のステップで、基準数が等しい要素の右側へ交換される可能性があります。
      -

      11.5.3   なぜクイックソートは高速なのか

      -

      名前が示すように、クイックソートは効率性の面で一定の利点を持つべきです。クイックソートの平均時間計算量は「マージソート」や「ヒープソート」と同じですが、以下の理由で一般的により効率的です。

      +

      11.5.3   クイックソートが速い理由

      +

      名前からも分かるように、クイックソートは効率面で一定の優位性を持っています。クイックソートの平均時間計算量は「マージソート」や「ヒープソート」と同じですが、通常はクイックソートのほうが高効率であり、主な理由は次のとおりです。

        -
      • 最悪ケースシナリオの低い確率:クイックソートの最悪時間計算量は\(O(n^2)\)で、マージソートほど安定していませんが、ほとんどの場合、クイックソートは\(O(n \log n)\)の時間計算量で動作できます。
      • -
      • 高いキャッシュ利用率:ピボット分割操作中、システムはサブ配列全体をキャッシュにロードできるため、要素により効率的にアクセスできます。対照的に、「ヒープソート」などのアルゴリズムは要素にジャンプ方式でアクセスする必要があり、この特徴を欠いています。
      • -
      • 計算量の小さな定数係数:上記3つのアルゴリズムの中で、クイックソートは比較、代入、交換などの操作の総数が最も少ないです。これは「挿入ソート」が「バブルソート」よりも高速な理由と似ています。
      • +
      • 最悪ケースが起こる確率が低い:クイックソートの最悪時間計算量は \(O(n^2)\) で、「マージソート」ほど安定ではありませんが、大半のケースでは \(O(n \log n)\) の時間計算量で動作します。
      • +
      • キャッシュ利用効率が高い:パーティション操作の実行時には、システムが部分配列全体をキャッシュに読み込めるため、要素アクセスの効率が高くなります。一方、「ヒープソート」のようなアルゴリズムは要素へ飛び飛びにアクセスする必要があり、この性質を持ちません。
      • +
      • 計算量の定数係数が小さい:上記 3 つのアルゴリズムの中で、クイックソートは比較、代入、交換などの操作総数が最も少なくなります。これは「挿入ソート」が「バブルソート」より速い理由と似ています。
      -

      11.5.4   ピボット最適化

      -

      クイックソートの時間効率は特定の入力で劣化する可能性があります。例えば、入力配列が完全に逆順の場合、最も左の要素をピボットとして選択するため、ピボット分割後、ピボットは配列の右端に交換され、左サブ配列の長さが\(n - 1\)、右サブ配列の長さが\(0\)になります。この方法を続けると、各ラウンドのピボット分割でサブ配列の長さが\(0\)になり、分割統治戦略が失敗し、クイックソートは「バブルソート」に似た形に劣化します。

      -

      この状況を避けるため、ピボット分割でピボット選択戦略を最適化できます。例えば、要素をランダムに選択してピボットとすることができます。ただし、運が悪く、一貫して最適でないピボットを選択した場合、効率はまだ満足できません。

      -

      プログラミング言語は通常「疑似乱数」を生成することに注意することが重要です。疑似乱数シーケンスに対して特定のテストケースを構築すると、クイックソートの効率はまだ劣化する可能性があります。

      -

      さらなる改善のため、3つの候補要素(通常は配列の最初、最後、中点の要素)を選択し、**これら3つの候補要素の中央値をピボットとして使用**できます。この方法で、ピボットが「小さすぎず大きすぎない」確率が大幅に増加します。もちろん、さらに多くの候補要素を選択してアルゴリズムの堅牢性をさらに向上させることもできます。この方法により、時間計算量が\(O(n^2)\)に劣化する確率が大幅に削減されます。

      -

      サンプルコードは以下の通りです:

      +

      11.5.4   基準数の最適化

      +

      クイックソートは、入力によっては時間効率が低下する可能性があります。極端な例として、入力配列が完全な逆順であるとします。最左端の要素を基準数として選ぶため、パーティション完了後には基準数が配列の最右端へ交換され、左部分配列の長さが \(n - 1\)、右部分配列の長さが \(0\) になります。この再帰を続けると、各回のパーティション後に必ず一方の部分配列の長さが \(0\) となり、分割統治戦略が機能せず、クイックソートは「バブルソート」に近い形へ退化します。

      +

      この状況をできるだけ避けるため、パーティションにおける基準数の選び方を最適化できます。たとえば、ランダムに 1 つの要素を選んで基準数にできます。しかし、運が悪く毎回望ましくない基準数を選んでしまうと、効率は依然として十分ではありません。

      +

      注意すべき点として、プログラミング言語が通常生成するのは「疑似乱数」です。疑似乱数列に合わせて特定のテストケースを構築すると、クイックソートの効率はやはり劣化する可能性があります。

      +

      さらに改善するために、配列から 3 つの候補要素(通常は先頭、末尾、中間の要素)を選び、**その 3 つの候補要素の中央値を基準数とする**ことができます。こうすると、基準数が「小さすぎず大きすぎもしない」確率が大幅に上がります。もちろん、候補要素をさらに増やして、アルゴリズムの頑健性をいっそう高めることも可能です。この方法を採用すると、時間計算量が \(O(n^2)\) まで劣化する確率は大きく下がります。

      +

      コード例を以下に示します。

      quick_sort.py
      def median_three(self, nums: list[int], left: int, mid: int, right: int) -> int:
      -    """3つの候補要素の中央値を選択"""
      +    """3つの候補要素の中央値を選ぶ"""
           l, m, r = nums[left], nums[mid], nums[right]
           if (l <= m <= r) or (r <= m <= l):
               return mid  # m は l と r の間
      @@ -4707,58 +5003,58 @@
           return right
       
       def partition(self, nums: list[int], left: int, right: int) -> int:
      -    """分割(三点中央値)"""
      -    # nums[left] をピボットとして使用
      +    """番兵による分割処理(3 点中央値)"""
      +    # nums[left] を基準値とする
           med = self.median_three(nums, left, (left + right) // 2, right)
      -    # 中央値を配列の最左端に交換
      +    # 中央値を配列の最左端に交換する
           nums[left], nums[med] = nums[med], nums[left]
      -    # nums[left] をピボットとして使用
      +    # nums[left] を基準値とする
           i, j = left, right
           while i < j:
               while i < j and nums[j] >= nums[left]:
      -            j -= 1  # 右から左へピボットより小さい最初の要素を探す
      +            j -= 1  # 右から左へ基準値未満の最初の要素を探す
               while i < j and nums[i] <= nums[left]:
      -            i += 1  # 左から右へピボットより大きい最初の要素を探す
      -        # 要素を交換
      +            i += 1  # 左から右へ基準値より大きい最初の要素を探す
      +        # 要素の交換
               nums[i], nums[j] = nums[j], nums[i]
      -    # ピボットを2つのサブ配列の境界に交換
      +    # 基準値を 2 つの部分配列の境界へ交換する
           nums[i], nums[left] = nums[left], nums[i]
      -    return i  # ピボットのインデックスを返す
      +    return i  # 基準値のインデックスを返す
       
      -
      quick_sort.cpp
      /* 三つの候補要素の中央値を選択 */
      +
      quick_sort.cpp
      /* 3つの候補要素の中央値を選ぶ */
       int medianThree(vector<int> &nums, int left, int mid, int right) {
           int l = nums[left], m = nums[mid], r = nums[right];
           if ((l <= m && m <= r) || (r <= m && m <= l))
      -        return mid; // mはlとrの間
      +        return mid; // m は l と r の間
           if ((m <= l && l <= r) || (r <= l && l <= m))
      -        return left; // lはmとrの間
      +        return left; // l は m と r の間
           return right;
       }
       
      -/* 分割(三つの中央値) */
      +/* 番兵による分割処理(3 点中央値) */
       int partition(vector<int> &nums, int left, int right) {
      -    // 三つの候補要素の中央値を選択
      +    // 3つの候補要素の中央値を選ぶ
           int med = medianThree(nums, left, (left + right) / 2, right);
      -    // 中央値を配列の最左位置に交換
      -    swap(nums, left, med);
      -    // nums[left]をピボットとして使用
      +    // 中央値を配列の最左端に交換する
      +    swap(nums[left], nums[med]);
      +    // nums[left] を基準値とする
           int i = left, j = right;
           while (i < j) {
               while (i < j && nums[j] >= nums[left])
      -            j--; // 右から左へピボットより小さい最初の要素を検索
      +            j--;                // 右から左へ基準値未満の最初の要素を探す
               while (i < j && nums[i] <= nums[left])
      -            i++;          // 左から右へピボットより大きい最初の要素を検索
      -        swap(nums, i, j); // これら二つの要素を交換
      +            i++;                // 左から右へ基準値より大きい最初の要素を探す
      +        swap(nums[i], nums[j]); // この 2 つの要素を交換
           }
      -    swap(nums, i, left); // ピボットを二つのサブ配列の境界に交換
      -    return i;            // ピボットのインデックスを返す
      +    swap(nums[i], nums[left]);  // 基準値を 2 つの部分配列の境界へ交換する
      +    return i;                   // 基準値のインデックスを返す
       }
       
      -
      quick_sort.java
      /* 3つの候補要素の中央値を選択 */
      +
      quick_sort.java
      /* 3つの候補要素の中央値を選ぶ */
       int medianThree(int[] nums, int left, int mid, int right) {
           int l = nums[left], m = nums[mid], r = nums[right];
           if ((l <= m && m <= r) || (r <= m && m <= l))
      @@ -4768,190 +5064,668 @@
           return right;
       }
       
      -/* 分割(3つの中央値) */
      +/* 番兵による分割処理(3 点中央値) */
       int partition(int[] nums, int left, int right) {
      -    // 3つの候補要素の中央値を選択
      +    // 3つの候補要素の中央値を選ぶ
           int med = medianThree(nums, left, (left + right) / 2, right);
      -    // 中央値を配列の最左端の位置に交換
      +    // 中央値を配列の最左端に交換する
           swap(nums, left, med);
      -    // nums[left] を基準値として使用
      +    // nums[left] を基準値とする
           int i = left, j = right;
           while (i < j) {
               while (i < j && nums[j] >= nums[left])
      -            j--;          // 右から左へ、基準値より小さい最初の要素を検索
      +            j--;          // 右から左へ基準値未満の最初の要素を探す
               while (i < j && nums[i] <= nums[left])
      -            i++;          // 左から右へ、基準値より大きい最初の要素を検索
      -        swap(nums, i, j); // これら2つの要素を交換
      +            i++;          // 左から右へ基準値より大きい最初の要素を探す
      +        swap(nums, i, j); // この 2 つの要素を交換
           }
      -    swap(nums, i, left);  // 基準値を2つの部分配列の境界に交換
      +    swap(nums, i, left);  // 基準値を 2 つの部分配列の境界へ交換する
           return i;             // 基準値のインデックスを返す
       }
       
      -
      quick_sort.cs
      [class]{QuickSortMedian}-[func]{MedianThree}
      -
      -[class]{QuickSortMedian}-[func]{Partition}
      +
      quick_sort.cs
      /* 3つの候補要素の中央値を選ぶ */
      +int MedianThree(int[] nums, int left, int mid, int right) {
      +    int l = nums[left], m = nums[mid], r = nums[right];
      +    if ((l <= m && m <= r) || (r <= m && m <= l))
      +        return mid; // m は l と r の間
      +    if ((m <= l && l <= r) || (r <= l && l <= m))
      +        return left; // l は m と r の間
      +    return right;
      +}
      +
      +/* 番兵による分割処理(3 点中央値) */
      +int Partition(int[] nums, int left, int right) {
      +    // 3つの候補要素の中央値を選ぶ
      +    int med = MedianThree(nums, left, (left + right) / 2, right);
      +    // 中央値を配列の最左端に交換する
      +    Swap(nums, left, med);
      +    // nums[left] を基準値とする
      +    int i = left, j = right;
      +    while (i < j) {
      +        while (i < j && nums[j] >= nums[left])
      +            j--;          // 右から左へ基準値未満の最初の要素を探す
      +        while (i < j && nums[i] <= nums[left])
      +            i++;          // 左から右へ基準値より大きい最初の要素を探す
      +        Swap(nums, i, j); // この 2 つの要素を交換
      +    }
      +    Swap(nums, i, left);  // 基準値を 2 つの部分配列の境界へ交換する
      +    return i;             // 基準値のインデックスを返す
      +}
       
      -
      quick_sort.go
      [class]{quickSortMedian}-[func]{medianThree}
      -
      -[class]{quickSortMedian}-[func]{partition}
      +
      quick_sort.go
      /* 3つの候補要素の中央値を選ぶ */
      +func (q *quickSortMedian) medianThree(nums []int, left, mid, right int) int {
      +    l, m, r := nums[left], nums[mid], nums[right]
      +    if (l <= m && m <= r) || (r <= m && m <= l) {
      +        return mid // m は l と r の間
      +    }
      +    if (m <= l && l <= r) || (r <= l && l <= m) {
      +        return left // l は m と r の間
      +    }
      +    return right
      +}
      +
      +/* 番兵による分割処理(3 点中央値) */
      +func (q *quickSortMedian) partition(nums []int, left, right int) int {
      +    // nums[left] を基準値とする
      +    med := q.medianThree(nums, left, (left+right)/2, right)
      +    // 中央値を配列の最左端に交換する
      +    nums[left], nums[med] = nums[med], nums[left]
      +    // nums[left] を基準値とする
      +    i, j := left, right
      +    for i < j {
      +        for i < j && nums[j] >= nums[left] {
      +            j-- // 右から左へ基準値未満の最初の要素を探す
      +        }
      +        for i < j && nums[i] <= nums[left] {
      +            i++ // 左から右へ基準値より大きい最初の要素を探す
      +        }
      +        // 要素の交換
      +        nums[i], nums[j] = nums[j], nums[i]
      +    }
      +    // 基準値を 2 つの部分配列の境界へ交換する
      +    nums[i], nums[left] = nums[left], nums[i]
      +    return i // 基準値のインデックスを返す
      +}
       
      -
      quick_sort.swift
      [class]{}-[func]{medianThree}
      -
      -[class]{}-[func]{partitionMedian}
      +
      quick_sort.swift
      /* 3つの候補要素の中央値を選ぶ */
      +func medianThree(nums: [Int], left: Int, mid: Int, right: Int) -> Int {
      +    let l = nums[left]
      +    let m = nums[mid]
      +    let r = nums[right]
      +    if (l <= m && m <= r) || (r <= m && m <= l) {
      +        return mid // m は l と r の間
      +    }
      +    if (m <= l && l <= r) || (r <= l && l <= m) {
      +        return left // l は m と r の間
      +    }
      +    return right
      +}
      +
      +/* 番兵による分割処理(3 点中央値) */
      +func partitionMedian(nums: inout [Int], left: Int, right: Int) -> Int {
      +    // 3つの候補要素の中央値を選ぶ
      +    let med = medianThree(nums: nums, left: left, mid: left + (right - left) / 2, right: right)
      +    // 中央値を配列の最左端に交換する
      +    nums.swapAt(left, med)
      +    return partition(nums: &nums, left: left, right: right)
      +}
       
      -
      quick_sort.js
      [class]{QuickSortMedian}-[func]{medianThree}
      -
      -[class]{QuickSortMedian}-[func]{partition}
      +
      quick_sort.js
      /* 3つの候補要素の中央値を選ぶ */
      +medianThree(nums, left, mid, right) {
      +    let l = nums[left],
      +        m = nums[mid],
      +        r = nums[right];
      +    // m は l と r の間
      +    if ((l <= m && m <= r) || (r <= m && m <= l)) return mid;
      +    // l は m と r の間
      +    if ((m <= l && l <= r) || (r <= l && l <= m)) return left;
      +    return right;
      +}
      +
      +/* 番兵による分割処理(3 点中央値) */
      +partition(nums, left, right) {
      +    // 3つの候補要素の中央値を選ぶ
      +    let med = this.medianThree(
      +        nums,
      +        left,
      +        Math.floor((left + right) / 2),
      +        right
      +    );
      +    // 中央値を配列の最左端に交換する
      +    this.swap(nums, left, med);
      +    // nums[left] を基準値とする
      +    let i = left,
      +        j = right;
      +    while (i < j) {
      +        while (i < j && nums[j] >= nums[left]) j--; // 右から左へ基準値未満の最初の要素を探す
      +        while (i < j && nums[i] <= nums[left]) i++; // 左から右へ基準値より大きい最初の要素を探す
      +        this.swap(nums, i, j); // この 2 つの要素を交換
      +    }
      +    this.swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する
      +    return i; // 基準値のインデックスを返す
      +}
       
      -
      quick_sort.ts
      [class]{QuickSortMedian}-[func]{medianThree}
      -
      -[class]{QuickSortMedian}-[func]{partition}
      +
      quick_sort.ts
      /* 3つの候補要素の中央値を選ぶ */
      +medianThree(
      +    nums: number[],
      +    left: number,
      +    mid: number,
      +    right: number
      +): number {
      +    let l = nums[left],
      +        m = nums[mid],
      +        r = nums[right];
      +    // m は l と r の間
      +    if ((l <= m && m <= r) || (r <= m && m <= l)) return mid;
      +    // l は m と r の間
      +    if ((m <= l && l <= r) || (r <= l && l <= m)) return left;
      +    return right;
      +}
      +
      +/* 番兵による分割処理(3 点中央値) */
      +partition(nums: number[], left: number, right: number): number {
      +    // 3つの候補要素の中央値を選ぶ
      +    let med = this.medianThree(
      +        nums,
      +        left,
      +        Math.floor((left + right) / 2),
      +        right
      +    );
      +    // 中央値を配列の最左端に交換する
      +    this.swap(nums, left, med);
      +    // nums[left] を基準値とする
      +    let i = left,
      +        j = right;
      +    while (i < j) {
      +        while (i < j && nums[j] >= nums[left]) {
      +            j--; // 右から左へ基準値未満の最初の要素を探す
      +        }
      +        while (i < j && nums[i] <= nums[left]) {
      +            i++; // 左から右へ基準値より大きい最初の要素を探す
      +        }
      +        this.swap(nums, i, j); // この 2 つの要素を交換
      +    }
      +    this.swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する
      +    return i; // 基準値のインデックスを返す
      +}
       
      -
      quick_sort.dart
      [class]{QuickSortMedian}-[func]{_medianThree}
      -
      -[class]{QuickSortMedian}-[func]{_partition}
      +
      quick_sort.dart
      /* 3つの候補要素の中央値を選ぶ */
      +int _medianThree(List<int> nums, int left, int mid, int right) {
      +  int l = nums[left], m = nums[mid], r = nums[right];
      +  if ((l <= m && m <= r) || (r <= m && m <= l))
      +    return mid; // m は l と r の間
      +  if ((m <= l && l <= r) || (r <= l && l <= m))
      +    return left; // l は m と r の間
      +  return right;
      +}
      +
      +/* 番兵による分割処理(3 点中央値) */
      +int _partition(List<int> nums, int left, int right) {
      +  // 3つの候補要素の中央値を選ぶ
      +  int med = _medianThree(nums, left, (left + right) ~/ 2, right);
      +  // 中央値を配列の最左端に交換する
      +  _swap(nums, left, med);
      +  // nums[left] を基準値とする
      +  int i = left, j = right;
      +  while (i < j) {
      +    while (i < j && nums[j] >= nums[left]) j--; // 右から左へ基準値未満の最初の要素を探す
      +    while (i < j && nums[i] <= nums[left]) i++; // 左から右へ基準値より大きい最初の要素を探す
      +    _swap(nums, i, j); // この 2 つの要素を交換
      +  }
      +  _swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する
      +  return i; // 基準値のインデックスを返す
      +}
       
      -
      quick_sort.rs
      [class]{QuickSortMedian}-[func]{median_three}
      -
      -[class]{QuickSortMedian}-[func]{partition}
      +
      quick_sort.rs
      /* 3つの候補要素の中央値を選ぶ */
      +fn median_three(nums: &mut [i32], left: usize, mid: usize, right: usize) -> usize {
      +    let (l, m, r) = (nums[left], nums[mid], nums[right]);
      +    if (l <= m && m <= r) || (r <= m && m <= l) {
      +        return mid; // m は l と r の間
      +    }
      +    if (m <= l && l <= r) || (r <= l && l <= m) {
      +        return left; // l は m と r の間
      +    }
      +    right
      +}
      +
      +/* 番兵による分割処理(3 点中央値) */
      +fn partition(nums: &mut [i32], left: usize, right: usize) -> usize {
      +    // 3つの候補要素の中央値を選ぶ
      +    let med = Self::median_three(nums, left, (left + right) / 2, right);
      +    // 中央値を配列の最左端に交換する
      +    nums.swap(left, med);
      +    // nums[left] を基準値とする
      +    let (mut i, mut j) = (left, right);
      +    while i < j {
      +        while i < j && nums[j] >= nums[left] {
      +            j -= 1; // 右から左へ基準値未満の最初の要素を探す
      +        }
      +        while i < j && nums[i] <= nums[left] {
      +            i += 1; // 左から右へ基準値より大きい最初の要素を探す
      +        }
      +        nums.swap(i, j); // この 2 つの要素を交換
      +    }
      +    nums.swap(i, left); // 基準値を 2 つの部分配列の境界へ交換する
      +    i // 基準値のインデックスを返す
      +}
       
      -
      quick_sort.c
      [class]{}-[func]{medianThree}
      -
      -[class]{}-[func]{partitionMedian}
      +
      quick_sort.c
      /* 3つの候補要素の中央値を選ぶ */
      +int medianThree(int nums[], int left, int mid, int right) {
      +    int l = nums[left], m = nums[mid], r = nums[right];
      +    if ((l <= m && m <= r) || (r <= m && m <= l))
      +        return mid; // m は l と r の間
      +    if ((m <= l && l <= r) || (r <= l && l <= m))
      +        return left; // l は m と r の間
      +    return right;
      +}
      +
      +/* 番兵による分割処理(3 点中央値) */
      +int partitionMedian(int nums[], int left, int right) {
      +    // 3つの候補要素の中央値を選ぶ
      +    int med = medianThree(nums, left, (left + right) / 2, right);
      +    // 中央値を配列の最左端に交換する
      +    swap(nums, left, med);
      +    // nums[left] を基準値とする
      +    int i = left, j = right;
      +    while (i < j) {
      +        while (i < j && nums[j] >= nums[left])
      +            j--; // 右から左へ基準値未満の最初の要素を探す
      +        while (i < j && nums[i] <= nums[left])
      +            i++;          // 左から右へ基準値より大きい最初の要素を探す
      +        swap(nums, i, j); // この 2 つの要素を交換
      +    }
      +    swap(nums, i, left); // 基準値を 2 つの部分配列の境界へ交換する
      +    return i;            // 基準値のインデックスを返す
      +}
       
      -
      quick_sort.kt
      [class]{}-[func]{medianThree}
      -
      -[class]{}-[func]{partitionMedian}
      +
      quick_sort.kt
      /* 3つの候補要素の中央値を選ぶ */
      +fun medianThree(nums: IntArray, left: Int, mid: Int, right: Int): Int {
      +    val l = nums[left]
      +    val m = nums[mid]
      +    val r = nums[right]
      +    if ((m in l..r) || (m in r..l))
      +        return mid  // m は l と r の間
      +    if ((l in m..r) || (l in r..m))
      +        return left // l は m と r の間
      +    return right
      +}
      +
      +/* 番兵による分割処理(3 点中央値) */
      +fun partitionMedian(nums: IntArray, left: Int, right: Int): Int {
      +    // 3つの候補要素の中央値を選ぶ
      +    val med = medianThree(nums, left, (left + right) / 2, right)
      +    // 中央値を配列の最左端に交換する
      +    swap(nums, left, med)
      +    // nums[left] を基準値とする
      +    var i = left
      +    var j = right
      +    while (i < j) {
      +        while (i < j && nums[j] >= nums[left])
      +            j--                      // 右から左へ基準値未満の最初の要素を探す
      +        while (i < j && nums[i] <= nums[left])
      +            i++                      // 左から右へ基準値より大きい最初の要素を探す
      +        swap(nums, i, j)             // この 2 つの要素を交換
      +    }
      +    swap(nums, i, left)              // 基準値を 2 つの部分配列の境界へ交換する
      +    return i                         // 基準値のインデックスを返す
      +}
       
      -
      quick_sort.rb
      [class]{QuickSortMedian}-[func]{median_three}
      -
      -[class]{QuickSortMedian}-[func]{partition}
      +
      quick_sort.rb
      ### 3 つの候補要素の中央値を選ぶ ###
      +def median_three(nums, left, mid, right)
      +  # 3つの候補要素の中央値を選ぶ
      +  _l, _m, _r = nums[left], nums[mid], nums[right]
      +  # m は l と r の間
      +  return mid if (_l <= _m && _m <= _r) || (_r <= _m && _m <= _l)
      +  # l は m と r の間
      +  return left if (_m <= _l && _l <= _r) || (_r <= _l && _l <= _m)
      +  return right
      +end
      +
      +### 3 つの候補要素の中央値を選ぶ ###
      +def median_three(nums, left, mid, right)
      +  # 3つの候補要素の中央値を選ぶ
      +  _l, _m, _r = nums[left], nums[mid], nums[right]
      +  # m は l と r の間
      +  return mid if (_l <= _m && _m <= _r) || (_r <= _m && _m <= _l)
      +  # l は m と r の間
      +  return left if (_m <= _l && _l <= _r) || (_r <= _l && _l <= _m)
      +  return right
      +end
      +
      +# ## 番兵分割(三数中央値)###
      +def partition(nums, left, right)
      +  # ## nums[left] を基準値とする
      +  med = median_three(nums, left, (left + right) / 2, right)
      +  # 中央値を配列の最左端に交換する
      +  nums[left], nums[med] = nums[med], nums[left]
      +  i, j = left, right
      +  while i < j
      +    while i < j && nums[j] >= nums[left]
      +      j -= 1 # 右から左へ基準値未満の最初の要素を探す
      +    end
      +    while i < j && nums[i] <= nums[left]
      +      i += 1 # 左から右へ基準値より大きい最初の要素を探す
      +    end
      +    # 要素の交換
      +    nums[i], nums[j] = nums[j], nums[i]
      +  end
      +  # 基準値を 2 つの部分配列の境界へ交換する
      +  nums[i], nums[left] = nums[left], nums[i]
      +  i # 基準値のインデックスを返す
      +end
       
      -

      11.5.5   末尾再帰最適化

      -

      特定の入力では、クイックソートはより多くの空間を占有する可能性があります。例えば、完全に順序付けられた入力配列を考えてみましょう。再帰でのサブ配列の長さを\(m\)とします。各ラウンドのピボット分割で、長さ\(0\)の左サブ配列と長さ\(m - 1\)の右サブ配列が生成されます。これは、再帰呼び出しごとに問題サイズが1つの要素のみ減少することを意味し、各レベルの再帰での削減が非常に小さくなります。 -結果として、再帰ツリーの高さは\(n − 1\)に達する可能性があり、これには\(O(n)\)のスタックフレーム空間が必要です。

      -

      スタックフレーム空間の蓄積を防ぐため、各ラウンドのピボットソート後に2つのサブ配列の長さを比較し、**より短いサブ配列のみを再帰的にソート**できます。より短いサブ配列の長さは\(n / 2\)を超えないため、この方法は再帰深度が\(\log n\)を超えないことを保証し、最悪空間計算量を\(O(\log n)\)に最適化します。コードは以下の通りです:

      +
      +コードの可視化 +

      +

      +
      +

      11.5.5   再帰の深さの最適化

      +

      一部の入力では、クイックソートは多くの空間を消費する可能性があります。完全に整列済みの入力配列を例にとり、再帰中の部分配列の長さを \(m\) とします。各回のパーティション操作では長さ \(0\) の左部分配列と長さ \(m - 1\) の右部分配列が生成されます。これは、各再帰呼び出しで減る問題サイズが非常に小さいこと(要素が 1 つ減るだけ)を意味し、再帰木の高さは \(n - 1\) に達するため、このとき \(O(n)\) のスタックフレーム空間を占有します。

      +

      スタックフレーム空間の蓄積を防ぐために、各回のパーティション完了後に 2 つの部分配列の長さを比較し、**短いほうの部分配列に対してのみ再帰**を行えます。短い部分配列の長さは \(n / 2\) を超えないため、この方法なら再帰の深さを \(\log n\) 以下に抑えられ、最悪時の空間計算量を \(O(\log n)\) まで最適化できます。コードを以下に示します。

      quick_sort.py
      def quick_sort(self, nums: list[int], left: int, right: int):
      -    """クイックソート(末尾再帰最適化)"""
      -    # サブ配列の長さが1のときに終了
      +    """クイックソート(再帰深度最適化)"""
      +    # 部分配列の長さが 1 なら終了
           while left < right:
      -        # 分割操作
      +        # 番兵による分割処理
               pivot = self.partition(nums, left, right)
      -        # 2つのサブ配列のうち短い方に対してクイックソートを実行
      +        # 2 つの部分配列のうち短いほうにクイックソートを適用する
               if pivot - left < right - pivot:
      -            self.quick_sort(nums, left, pivot - 1)  # 左サブ配列を再帰的にソート
      -            left = pivot + 1  # 残りの未ソート区間は [pivot + 1, right]
      +            self.quick_sort(nums, left, pivot - 1)  # 左部分配列を再帰的にソート
      +            left = pivot + 1  # 未ソート区間の残りは [pivot + 1, right]
               else:
      -            self.quick_sort(nums, pivot + 1, right)  # 右サブ配列を再帰的にソート
      -            right = pivot - 1  # 残りの未ソート区間は [left, pivot - 1]
      +            self.quick_sort(nums, pivot + 1, right)  # 右部分配列を再帰的にソート
      +            right = pivot - 1  # 未ソート区間の残りは [left, pivot - 1]
       
      -
      quick_sort.cpp
      /* クイックソート(末尾再帰最適化) */
      +
      quick_sort.cpp
      /* クイックソート(再帰深度最適化) */
       void quickSort(vector<int> &nums, int left, int right) {
      -    // サブ配列の長さが1の時終了
      +    // 部分配列の長さが 1 なら終了
           while (left < right) {
      -        // 分割操作
      +        // 番兵による分割処理
               int pivot = partition(nums, left, right);
      -        // 二つのサブ配列のうち短い方でクイックソートを実行
      +        // 2 つの部分配列のうち短いほうにクイックソートを適用する
               if (pivot - left < right - pivot) {
      -            quickSort(nums, left, pivot - 1); // 左サブ配列を再帰的にソート
      -            left = pivot + 1;                 // 残りの未ソート区間は[pivot + 1, right]
      +            quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソート
      +            left = pivot + 1;                 // 未ソート区間の残りは [pivot + 1, right]
               } else {
      -            quickSort(nums, pivot + 1, right); // 右サブ配列を再帰的にソート
      -            right = pivot - 1;                 // 残りの未ソート区間は[left, pivot - 1]
      +            quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソート
      +            right = pivot - 1;                 // 未ソート区間の残りは [left, pivot - 1]
               }
           }
       }
       
      -
      quick_sort.java
      /* クイックソート(末尾再帰最適化) */
      +
      quick_sort.java
      /* クイックソート(再帰深度最適化) */
       void quickSort(int[] nums, int left, int right) {
      -    // 部分配列の長さが 1 のとき終了
      +    // 部分配列の長さが 1 なら終了
           while (left < right) {
      -        // 分割操作
      +        // 番兵による分割処理
               int pivot = partition(nums, left, right);
      -        // 2つの部分配列のうち短い方にクイックソートを実行
      +        // 2 つの部分配列のうち短いほうにクイックソートを適用する
               if (pivot - left < right - pivot) {
                   quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソート
      -            left = pivot + 1; // 残りの未ソート区間は [pivot + 1, right]
      +            left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right]
               } else {
                   quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソート
      -            right = pivot - 1; // 残りの未ソート区間は [left, pivot - 1]
      +            right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1]
               }
           }
       }
       
      -
      quick_sort.cs
      [class]{QuickSortTailCall}-[func]{QuickSort}
      +
      quick_sort.cs
      /* クイックソート(再帰深度最適化) */
      +void QuickSort(int[] nums, int left, int right) {
      +    // 部分配列の長さが 1 なら終了
      +    while (left < right) {
      +        // 番兵による分割処理
      +        int pivot = Partition(nums, left, right);
      +        // 2 つの部分配列のうち短いほうにクイックソートを適用する
      +        if (pivot - left < right - pivot) {
      +            QuickSort(nums, left, pivot - 1);  // 左部分配列を再帰的にソート
      +            left = pivot + 1;  // 未ソート区間の残りは [pivot + 1, right]
      +        } else {
      +            QuickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソート
      +            right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1]
      +        }
      +    }
      +}
       
      -
      quick_sort.go
      [class]{quickSortTailCall}-[func]{quickSort}
      +
      quick_sort.go
      /* クイックソート(再帰深度最適化) */
      +func (q *quickSortTailCall) quickSort(nums []int, left, right int) {
      +    // 部分配列の長さが 1 なら終了
      +    for left < right {
      +        // 番兵による分割処理
      +        pivot := q.partition(nums, left, right)
      +        // 2 つの部分配列のうち短いほうにクイックソートを適用する
      +        if pivot-left < right-pivot {
      +            q.quickSort(nums, left, pivot-1) // 左部分配列を再帰的にソート
      +            left = pivot + 1                 // 未ソート区間の残りは [pivot + 1, right]
      +        } else {
      +            q.quickSort(nums, pivot+1, right) // 右部分配列を再帰的にソート
      +            right = pivot - 1                 // 未ソート区間の残りは [left, pivot - 1]
      +        }
      +    }
      +}
       
      -
      quick_sort.swift
      [class]{}-[func]{quickSortTailCall}
      +
      quick_sort.swift
      /* クイックソート(再帰深度最適化) */
      +func quickSortTailCall(nums: inout [Int], left: Int, right: Int) {
      +    var left = left
      +    var right = right
      +    // 部分配列の長さが 1 なら終了
      +    while left < right {
      +        // 番兵による分割処理
      +        let pivot = partition(nums: &nums, left: left, right: right)
      +        // 2 つの部分配列のうち短いほうにクイックソートを適用する
      +        if (pivot - left) < (right - pivot) {
      +            quickSortTailCall(nums: &nums, left: left, right: pivot - 1) // 左部分配列を再帰的にソート
      +            left = pivot + 1 // 未ソート区間の残りは [pivot + 1, right]
      +        } else {
      +            quickSortTailCall(nums: &nums, left: pivot + 1, right: right) // 右部分配列を再帰的にソート
      +            right = pivot - 1 // 未ソート区間の残りは [left, pivot - 1]
      +        }
      +    }
      +}
       
      -
      quick_sort.js
      [class]{QuickSortTailCall}-[func]{quickSort}
      +
      quick_sort.js
      /* クイックソート(再帰深度最適化) */
      +quickSort(nums, left, right) {
      +    // 部分配列の長さが 1 なら終了
      +    while (left < right) {
      +        // 番兵による分割処理
      +        let pivot = this.partition(nums, left, right);
      +        // 2 つの部分配列のうち短いほうにクイックソートを適用する
      +        if (pivot - left < right - pivot) {
      +            this.quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソート
      +            left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right]
      +        } else {
      +            this.quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソート
      +            right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1]
      +        }
      +    }
      +}
       
      -
      quick_sort.ts
      [class]{QuickSortTailCall}-[func]{quickSort}
      +
      quick_sort.ts
      /* クイックソート(再帰深度最適化) */
      +quickSort(nums: number[], left: number, right: number): void {
      +    // 部分配列の長さが 1 なら終了
      +    while (left < right) {
      +        // 番兵による分割処理
      +        let pivot = this.partition(nums, left, right);
      +        // 2 つの部分配列のうち短いほうにクイックソートを適用する
      +        if (pivot - left < right - pivot) {
      +            this.quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソート
      +            left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right]
      +        } else {
      +            this.quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソート
      +            right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1]
      +        }
      +    }
      +}
       
      -
      quick_sort.dart
      [class]{QuickSortTailCall}-[func]{quickSort}
      +
      quick_sort.dart
      /* クイックソート(再帰深度最適化) */
      +void quickSort(List<int> nums, int left, int right) {
      +  // 部分配列の長さが 1 なら終了
      +  while (left < right) {
      +    // 番兵による分割処理
      +    int pivot = _partition(nums, left, right);
      +    // 2 つの部分配列のうち短いほうにクイックソートを適用する
      +    if (pivot - left < right - pivot) {
      +      quickSort(nums, left, pivot - 1); // 左部分配列を再帰的にソート
      +      left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right]
      +    } else {
      +      quickSort(nums, pivot + 1, right); // 右部分配列を再帰的にソート
      +      right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1]
      +    }
      +  }
      +}
       
      -
      quick_sort.rs
      [class]{QuickSortTailCall}-[func]{quick_sort}
      +
      quick_sort.rs
      /* クイックソート(再帰深度最適化) */
      +pub fn quick_sort(mut left: i32, mut right: i32, nums: &mut [i32]) {
      +    // 部分配列の長さが 1 なら終了
      +    while left < right {
      +        // 番兵による分割処理
      +        let pivot = Self::partition(nums, left as usize, right as usize) as i32;
      +        // 2 つの部分配列のうち短いほうにクイックソートを適用する
      +        if pivot - left < right - pivot {
      +            Self::quick_sort(left, pivot - 1, nums); // 左部分配列を再帰的にソート
      +            left = pivot + 1; // 未ソート区間の残りは [pivot + 1, right]
      +        } else {
      +            Self::quick_sort(pivot + 1, right, nums); // 右部分配列を再帰的にソート
      +            right = pivot - 1; // 未ソート区間の残りは [left, pivot - 1]
      +        }
      +    }
      +}
       
      -
      quick_sort.c
      [class]{}-[func]{quickSortTailCall}
      +
      quick_sort.c
      /* クイックソート(再帰深度最適化) */
      +void quickSortTailCall(int nums[], int left, int right) {
      +    // 部分配列の長さが 1 なら終了
      +    while (left < right) {
      +        // 番兵による分割処理
      +        int pivot = partition(nums, left, right);
      +        // 2 つの部分配列のうち短いほうにクイックソートを適用する
      +        if (pivot - left < right - pivot) {
      +            // 左部分配列を再帰的にソート
      +            quickSortTailCall(nums, left, pivot - 1);
      +            // 未ソート区間の残りは [pivot + 1, right]
      +            left = pivot + 1;
      +        } else {
      +            // 右部分配列を再帰的にソート
      +            quickSortTailCall(nums, pivot + 1, right);
      +            // 未ソート区間の残りは [left, pivot - 1]
      +            right = pivot - 1;
      +        }
      +    }
      +}
       
      -
      quick_sort.kt
      [class]{}-[func]{quickSortTailCall}
      +
      quick_sort.kt
      /* クイックソート(再帰深度最適化) */
      +fun quickSortTailCall(nums: IntArray, left: Int, right: Int) {
      +    // 部分配列の長さが 1 なら終了
      +    var l = left
      +    var r = right
      +    while (l < r) {
      +        // 番兵による分割処理
      +        val pivot = partition(nums, l, r)
      +        // 2 つの部分配列のうち短いほうにクイックソートを適用する
      +        if (pivot - l < r - pivot) {
      +            quickSort(nums, l, pivot - 1) // 左部分配列を再帰的にソート
      +            l = pivot + 1 // 未ソート区間の残りは [pivot + 1, right]
      +        } else {
      +            quickSort(nums, pivot + 1, r) // 右部分配列を再帰的にソート
      +            r = pivot - 1 // 未ソート区間の残りは [left, pivot - 1]
      +        }
      +    }
      +}
       
      -
      quick_sort.rb
      [class]{QuickSortTailCall}-[func]{quick_sort}
      +
      quick_sort.rb
      ### 番兵分割 ###
      +def partition(nums, left, right)
      +  # nums[left] を基準値とする
      +  i = left
      +  j = right
      +  while i < j
      +    while i < j && nums[j] >= nums[left]
      +      j -= 1 # 右から左へ基準値未満の最初の要素を探す
      +    end
      +    while i < j && nums[i] <= nums[left]
      +      i += 1 # 左から右へ基準値より大きい最初の要素を探す
      +    end
      +    # 要素の交換
      +    nums[i], nums[j] = nums[j], nums[i]
      +  end
      +  # 基準値を 2 つの部分配列の境界へ交換する
      +  nums[i], nums[left] = nums[left], nums[i]
      +  i # 基準値のインデックスを返す
      +end
      +
      +# ## クイックソート(再帰深度最適化)###
      +def quick_sort(nums, left, right)
      +  # 部分配列の長さが 1 でない場合は再帰する
      +  while left < right
      +    # 番兵分割
      +    pivot = partition(nums, left, right)
      +    # 2 つの部分配列のうち短いほうにクイックソートを適用する
      +    if pivot - left < right - pivot
      +      quick_sort(nums, left, pivot - 1)
      +      left = pivot + 1 # 未ソート区間の残りは [pivot + 1, right]
      +    else
      +      quick_sort(nums, pivot + 1, right)
      +      right = pivot - 1 # 未ソート区間の残りは [left, pivot - 1]
      +    end
      +  end
      +end
       
      +
      +コードの可視化 +

      +

      +
      diff --git a/ja/chapter_sorting/radix_sort/index.html b/ja/chapter_sorting/radix_sort/index.html index e60293b7c..f73d9162f 100644 --- a/ja/chapter_sorting/radix_sort/index.html +++ b/ja/chapter_sorting/radix_sort/index.html @@ -6,7 +6,7 @@ - + @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1591,7 +1591,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1613,7 +1613,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1635,7 +1635,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1942,7 +1942,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2105,7 +2105,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2491,7 +2491,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2547,7 +2547,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2575,7 +2575,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -3035,7 +3035,7 @@ - 11.10.1   アルゴリズムの過程 + 11.10.1   アルゴリズムの流れ @@ -3185,7 +3185,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3241,7 +3241,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3269,7 +3269,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3462,7 +3462,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3490,7 +3490,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3631,7 +3631,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3659,7 +3659,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3687,7 +3687,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3715,7 +3715,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3908,7 +3908,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4153,7 +4153,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4299,7 +4299,7 @@ - 11.10.1   アルゴリズムの過程 + 11.10.1   アルゴリズムの流れ @@ -4355,262 +4355,709 @@

      11.10   基数ソート

      -

      前の節では計数ソートを紹介しました。これは、データサイズ \(n\) が大きいがデータ範囲 \(m\) が小さいシナリオに適しています。\(n = 10^6\) の学生IDをソートする必要があり、各IDが \(8\) 桁の数字であるとします。これは、データ範囲 \(m = 10^8\) が非常に大きいことを意味します。この場合、計数ソートを使用すると、大量のメモリスペースが必要になります。基数ソートはこの状況を回避できます。

      -

      基数ソートは計数ソートと同じ核心概念を共有し、要素の頻度をカウントすることでソートします。同時に、基数ソートは数字の桁間の漸進的関係を利用してこれを基盤としています。桁を一度に一つずつ処理してソートし、最終的なソート順序を達成します。

      -

      11.10.1   アルゴリズムの過程

      -

      学生IDデータを例として、最下位桁を \(1\) 番目、最上位桁を \(8\) 番目とすると、基数ソートの過程は以下の図に示されています。

      +

      前節では計数ソートを紹介しました。これは、データ量 \(n\) が大きく、データ範囲 \(m\) が小さい場合に適しています。\(n = 10^6\) 個の学籍番号をソートすると仮定すると、学籍番号は \(8\) 桁の数字なので、データ範囲 \(m = 10^8\) は非常に大きくなります。計数ソートでは大量のメモリ空間を確保する必要がありますが、基数ソートではこの問題を回避できます。

      +

      基数ソート(radix sort)の基本的な考え方は計数ソートと同じで、個数を数えることによってソートを実現します。そのうえで、基数ソートは各桁の段階的な関係を利用し、各桁を順にソートすることで、最終的なソート結果を得ます。

      +

      11.10.1   アルゴリズムの流れ

      +

      学籍番号データを例にすると、数字の最下位桁を第 \(1\) 位、最上位桁を第 \(8\) 位としたとき、基数ソートの流れは次図のようになります。

        -
      1. \(k = 1\) を初期化します。
      2. -
      3. 学生IDの \(k\) 番目の桁に対して「計数ソート」を実行します。完了後、データは \(k\) 番目の桁に基づいて最小から最大までソートされます。
      4. -
      5. \(k\)\(1\) 増やし、手順 2. に戻って反復を続け、すべての桁がソートされるまで続けます。この時点で過程が終了します。
      6. +
      7. 桁番号 \(k = 1\) を初期化します。
      8. +
      9. 学籍番号の第 \(k\) 位に対して「計数ソート」を実行します。完了すると、データは第 \(k\) 位に従って昇順に並びます。
      10. +
      11. \(k\)\(1\) 増やし、手順 2. に戻って反復を続けます。すべての桁のソートが完了したら終了します。
      -

      基数ソートアルゴリズムの過程

      -

      図 11-18   基数ソートアルゴリズムの過程

      +

      基数ソートのアルゴリズムの流れ

      +

      図 11-18   基数ソートのアルゴリズムの流れ

      -

      以下、コード実装を詳しく見てみます。基数 \(d\) での数 \(x\) に対して、その \(k\) 番目の桁 \(x_k\) を取得するには、以下の計算式を使用できます:

      +

      以下ではコード実装を分解して見ていきます。\(d\) 進数の数値 \(x\) について、その第 \(k\)\(x_k\) を取得するには、次の計算式を用います。

      \[ x_k = \lfloor\frac{x}{d^{k-1}}\rfloor \bmod d \]
      -

      ここで \(\lfloor a \rfloor\) は浮動小数点数 \(a\) の切り捨てを表し、\(\bmod \: d\)\(d\) による剰余を表します。学生IDデータの場合、\(d = 10\)\(k \in [1, 8]\) です。

      -

      さらに、\(k\) 番目の桁に基づいてソートできるように、計数ソートのコードを少し修正する必要があります:

      +

      ここで、\(\lfloor a \rfloor\) は浮動小数点数 \(a\) の切り捨てを表し、\(\bmod \: d\)\(d\) による剰余を表します。学籍番号データでは、\(d = 10\) かつ \(k \in [1, 8]\) です。

      +

      さらに、数字の第 \(k\) 位に基づいてソートできるように、計数ソートのコードを少し変更する必要があります。

      radix_sort.py
      def digit(num: int, exp: int) -> int:
      -    """要素 num の k 番目の桁を取得、exp = 10^(k-1)"""
      -    # k の代わりに exp を渡すことで、ここでコストの高い累乗計算を避けることができる
      +    """要素 num の下から k 桁目を取得(exp = 10^(k-1))"""
      +    # ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す
           return (num // exp) % 10
       
       def counting_sort_digit(nums: list[int], exp: int):
      -    """計数ソート(nums の k 番目の桁に基づく)"""
      -    # 10進数の桁の範囲は 0~9、したがって長さ10のバケット配列が必要
      +    """計数ソート(nums の k 桁目でソート)"""
      +    # 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要
           counter = [0] * 10
           n = len(nums)
      -    # 数字 0~9 の出現回数を統計
      +    # 0~9 の各数字の出現回数を集計する
           for i in range(n):
      -        d = digit(nums[i], exp)  # nums[i] の k 番目の桁を取得、d とする
      -        counter[d] += 1  # 数字 d の出現回数を統計
      -    # 前置和を計算し、「出現回数」を「配列インデックス」に変換
      +        d = digit(nums[i], exp)  # nums[i] の第 k 位を取得し、d とする
      +        counter[d] += 1  # 数字 d の出現回数を数える
      +    # 累積和を求め、「出現回数」を「配列インデックス」に変換する
           for i in range(1, 10):
               counter[i] += counter[i - 1]
      -    # 逆順に走査し、バケット統計に基づいて各要素を res に配置
      +    # 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する
           res = [0] * n
           for i in range(n - 1, -1, -1):
               d = digit(nums[i], exp)
      -        j = counter[d] - 1  # 配列内の d のインデックス j を取得
      -        res[j] = nums[i]  # 現在の要素をインデックス j に配置
      -        counter[d] -= 1  # d の数を1減らす
      -    # 結果を使用して元の配列 nums を上書き
      +        j = counter[d] - 1  # d の配列内インデックス j を取得する
      +        res[j] = nums[i]  # 現在の要素をインデックス j に格納する
      +        counter[d] -= 1  # d の個数を 1 減らす
      +    # 結果で元の配列 nums を上書きする
           for i in range(n):
               nums[i] = res[i]
       
       def radix_sort(nums: list[int]):
           """基数ソート"""
      -    # 配列の最大要素を取得し、最大桁数を判定するために使用
      +    # 最大桁数の判定用に配列の最大要素を取得
           m = max(nums)
      -    # 最下位桁から最上位桁まで走査
      +    # 下位桁から上位桁の順に走査する
           exp = 1
           while exp <= m:
      -        # 配列要素の k 番目の桁に対して計数ソートを実行
      +        # 配列要素の k 桁目に対して計数ソートを行う
               # k = 1 -> exp = 1
               # k = 2 -> exp = 10
      -        # つまり、exp = 10^(k-1)
      +        # つまり exp = 10^(k-1)
               counting_sort_digit(nums, exp)
               exp *= 10
       
      -
      radix_sort.cpp
      /* 要素numのk番目の桁を取得、exp = 10^(k-1) */
      +
      radix_sort.cpp
      /* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */
       int digit(int num, int exp) {
      -    // kの代わりにexpを渡すことで、ここで繰り返される高価な冪乗計算を避けることができる
      +    // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す
           return (num / exp) % 10;
       }
       
      -/* カウントソート(numsのk番目の桁に基づく) */
      +/* 計数ソート(nums の k 桁目でソート) */
       void countingSortDigit(vector<int> &nums, int exp) {
      -    // 10進数の桁範囲は0~9なので、長さ10のバケット配列が必要
      +    // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要
           vector<int> counter(10, 0);
           int n = nums.size();
      -    // 数字0~9の出現回数を統計
      +    // 0~9 の各数字の出現回数を集計する
           for (int i = 0; i < n; i++) {
      -        int d = digit(nums[i], exp); // nums[i]のk番目の桁を取得、dとして記録
      -        counter[d]++;                // 数字dの出現回数を統計
      +        int d = digit(nums[i], exp); // nums[i] の第 k 位を取得し、d とする
      +        counter[d]++;                // 数字 d の出現回数を数える
           }
      -    // 前缀和を計算し、「出現回数」を「配列インデックス」に変換
      +    // 累積和を求め、「出現回数」を「配列インデックス」に変換する
           for (int i = 1; i < 10; i++) {
               counter[i] += counter[i - 1];
           }
      -    // 逆順で走査し、バケット統計に基づいて各要素をresに配置
      +    // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する
           vector<int> res(n, 0);
           for (int i = n - 1; i >= 0; i--) {
               int d = digit(nums[i], exp);
      -        int j = counter[d] - 1; // dが配列内にあるインデックスjを取得
      -        res[j] = nums[i];       // 現在の要素をインデックスjに配置
      -        counter[d]--;           // dのカウントを1減らす
      +        int j = counter[d] - 1; // d の配列内インデックス j を取得する
      +        res[j] = nums[i];       // 現在の要素をインデックス j に格納する
      +        counter[d]--;           // d の個数を 1 減らす
           }
      -    // 結果で元の配列numsを上書き
      +    // 結果で元の配列 nums を上書きする
           for (int i = 0; i < n; i++)
               nums[i] = res[i];
       }
       
       /* 基数ソート */
       void radixSort(vector<int> &nums) {
      -    // 配列の最大要素を取得、最大桁数を判定するために使用
      +    // 最大桁数の判定用に配列の最大要素を取得
           int m = *max_element(nums.begin(), nums.end());
      -    // 最下位桁から最上位桁まで走査
      +    // 下位桁から上位桁の順に走査する
           for (int exp = 1; exp <= m; exp *= 10)
      -        // 配列要素のk番目の桁でカウントソートを実行
      +        // 配列要素の k 桁目に対して計数ソートを行う
               // k = 1 -> exp = 1
               // k = 2 -> exp = 10
      -        // つまり、exp = 10^(k-1)
      +        // つまり exp = 10^(k-1)
               countingSortDigit(nums, exp);
       }
       
      -
      radix_sort.java
      /* 要素 num の k 番目の桁を取得、exp = 10^(k-1) */
      +
      radix_sort.java
      /* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */
       int digit(int num, int exp) {
      -    // k の代わりに exp を渡すことで、ここでコストの高い累乗計算の繰り返しを避けることができる
      +    // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す
           return (num / exp) % 10;
       }
       
      -/* 計数ソート(nums の k 番目の桁に基づく) */
      +/* 計数ソート(nums の k 桁目でソート) */
       void countingSortDigit(int[] nums, int exp) {
      -    // 10進数の桁の範囲は 0~9、したがって長さ 10 のバケット配列が必要
      +    // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要
           int[] counter = new int[10];
           int n = nums.length;
      -    // 桁 0~9 の出現回数を統計
      +    // 0~9 の各数字の出現回数を集計する
           for (int i = 0; i < n; i++) {
      -        int d = digit(nums[i], exp); // nums[i] の k 番目の桁を取得、d とする
      -        counter[d]++;                // 桁 d の出現回数を統計
      +        int d = digit(nums[i], exp); // nums[i] の第 k 位を取得し、d とする
      +        counter[d]++;                // 数字 d の出現回数を数える
           }
      -    // 累積和を計算し、「出現回数」を「配列インデックス」に変換
      +    // 累積和を求め、「出現回数」を「配列インデックス」に変換する
           for (int i = 1; i < 10; i++) {
               counter[i] += counter[i - 1];
           }
      -    // 逆順に走査し、バケット統計に基づいて各要素を res に配置
      +    // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する
           int[] res = new int[n];
           for (int i = n - 1; i >= 0; i--) {
               int d = digit(nums[i], exp);
      -        int j = counter[d] - 1; // 配列内での d のインデックス j を取得
      -        res[j] = nums[i];       // 現在の要素をインデックス j に配置
      -        counter[d]--;           // d のカウントを 1 減らす
      +        int j = counter[d] - 1; // d の配列内インデックス j を取得する
      +        res[j] = nums[i];       // 現在の要素をインデックス j に格納する
      +        counter[d]--;           // d の個数を 1 減らす
           }
      -    // 結果で元の配列 nums を上書き
      +    // 結果で元の配列 nums を上書きする
           for (int i = 0; i < n; i++)
               nums[i] = res[i];
       }
       
       /* 基数ソート */
       void radixSort(int[] nums) {
      -    // 配列の最大要素を取得し、最大桁数を判定するために使用
      +    // 最大桁数の判定用に配列の最大要素を取得
           int m = Integer.MIN_VALUE;
           for (int num : nums)
               if (num > m)
                   m = num;
      -    // 最下位桁から最上位桁まで走査
      +    // 下位桁から上位桁の順に走査する
           for (int exp = 1; exp <= m; exp *= 10) {
      -        // 配列要素の k 番目の桁に対して計数ソートを実行
      +        // 配列要素の k 桁目に対して計数ソートを行う
               // k = 1 -> exp = 1
               // k = 2 -> exp = 10
      -        // すなわち exp = 10^(k-1)
      +        // つまり exp = 10^(k-1)
               countingSortDigit(nums, exp);
           }
       }
       
      -
      radix_sort.cs
      [class]{radix_sort}-[func]{Digit}
      -
      -[class]{radix_sort}-[func]{CountingSortDigit}
      -
      -[class]{radix_sort}-[func]{RadixSort}
      +
      radix_sort.cs
      /* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */
      +int Digit(int num, int exp) {
      +    // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す
      +    return (num / exp) % 10;
      +}
      +
      +/* 計数ソート(nums の k 桁目でソート) */
      +void CountingSortDigit(int[] nums, int exp) {
      +    // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要
      +    int[] counter = new int[10];
      +    int n = nums.Length;
      +    // 0~9 の各数字の出現回数を集計する
      +    for (int i = 0; i < n; i++) {
      +        int d = Digit(nums[i], exp); // nums[i] の第 k 位を取得し、d とする
      +        counter[d]++;                // 数字 d の出現回数を数える
      +    }
      +    // 累積和を求め、「出現回数」を「配列インデックス」に変換する
      +    for (int i = 1; i < 10; i++) {
      +        counter[i] += counter[i - 1];
      +    }
      +    // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する
      +    int[] res = new int[n];
      +    for (int i = n - 1; i >= 0; i--) {
      +        int d = Digit(nums[i], exp);
      +        int j = counter[d] - 1; // d の配列内インデックス j を取得する
      +        res[j] = nums[i];       // 現在の要素をインデックス j に格納する
      +        counter[d]--;           // d の個数を 1 減らす
      +    }
      +    // 結果で元の配列 nums を上書きする
      +    for (int i = 0; i < n; i++) {
      +        nums[i] = res[i];
      +    }
      +}
      +
      +/* 基数ソート */
      +void RadixSort(int[] nums) {
      +    // 最大桁数の判定用に配列の最大要素を取得
      +    int m = int.MinValue;
      +    foreach (int num in nums) {
      +        if (num > m) m = num;
      +    }
      +    // 下位桁から上位桁の順に走査する
      +    for (int exp = 1; exp <= m; exp *= 10) {
      +        // 配列要素の k 桁目に対して計数ソートを行う
      +        // k = 1 -> exp = 1
      +        // k = 2 -> exp = 10
      +        // つまり exp = 10^(k-1)
      +        CountingSortDigit(nums, exp);
      +    }
      +}
       
      -
      radix_sort.go
      [class]{}-[func]{digit}
      -
      -[class]{}-[func]{countingSortDigit}
      -
      -[class]{}-[func]{radixSort}
      +
      radix_sort.go
      /* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */
      +func digit(num, exp int) int {
      +    // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す
      +    return (num / exp) % 10
      +}
      +
      +/* 計数ソート(nums の k 桁目でソート) */
      +func countingSortDigit(nums []int, exp int) {
      +    // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要
      +    counter := make([]int, 10)
      +    n := len(nums)
      +    // 0~9 の各数字の出現回数を集計する
      +    for i := 0; i < n; i++ {
      +        d := digit(nums[i], exp) // nums[i] の第 k 位を取得し、d とする
      +        counter[d]++             // 数字 d の出現回数を数える
      +    }
      +    // 累積和を求め、「出現回数」を「配列インデックス」に変換する
      +    for i := 1; i < 10; i++ {
      +        counter[i] += counter[i-1]
      +    }
      +    // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する
      +    res := make([]int, n)
      +    for i := n - 1; i >= 0; i-- {
      +        d := digit(nums[i], exp)
      +        j := counter[d] - 1 // d の配列内インデックス j を取得する
      +        res[j] = nums[i]    // 現在の要素をインデックス j に格納する
      +        counter[d]--        // d の個数を 1 減らす
      +    }
      +    // 結果で元の配列 nums を上書きする
      +    for i := 0; i < n; i++ {
      +        nums[i] = res[i]
      +    }
      +}
      +
      +/* 基数ソート */
      +func radixSort(nums []int) {
      +    // 最大桁数の判定用に配列の最大要素を取得
      +    max := math.MinInt
      +    for _, num := range nums {
      +        if num > max {
      +            max = num
      +        }
      +    }
      +    // 下位桁から上位桁の順に走査する
      +    for exp := 1; max >= exp; exp *= 10 {
      +        // 配列要素の k 桁目に対して計数ソートを行う
      +        // k = 1 -> exp = 1
      +        // k = 2 -> exp = 10
      +        // つまり exp = 10^(k-1)
      +        countingSortDigit(nums, exp)
      +    }
      +}
       
      -
      radix_sort.swift
      [class]{}-[func]{digit}
      -
      -[class]{}-[func]{countingSortDigit}
      -
      -[class]{}-[func]{radixSort}
      +
      radix_sort.swift
      /* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */
      +func digit(num: Int, exp: Int) -> Int {
      +    // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す
      +    (num / exp) % 10
      +}
      +
      +/* 計数ソート(nums の k 桁目でソート) */
      +func countingSortDigit(nums: inout [Int], exp: Int) {
      +    // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要
      +    var counter = Array(repeating: 0, count: 10)
      +    // 0~9 の各数字の出現回数を集計する
      +    for i in nums.indices {
      +        let d = digit(num: nums[i], exp: exp) // nums[i] の第 k 位を取得し、d とする
      +        counter[d] += 1 // 数字 d の出現回数を数える
      +    }
      +    // 累積和を求め、「出現回数」を「配列インデックス」に変換する
      +    for i in 1 ..< 10 {
      +        counter[i] += counter[i - 1]
      +    }
      +    // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する
      +    var res = Array(repeating: 0, count: nums.count)
      +    for i in nums.indices.reversed() {
      +        let d = digit(num: nums[i], exp: exp)
      +        let j = counter[d] - 1 // d の配列内インデックス j を取得する
      +        res[j] = nums[i] // 現在の要素をインデックス j に格納する
      +        counter[d] -= 1 // d の個数を 1 減らす
      +    }
      +    // 結果で元の配列 nums を上書きする
      +    for i in nums.indices {
      +        nums[i] = res[i]
      +    }
      +}
      +
      +/* 基数ソート */
      +func radixSort(nums: inout [Int]) {
      +    // 最大桁数の判定用に配列の最大要素を取得
      +    var m = Int.min
      +    for num in nums {
      +        if num > m {
      +            m = num
      +        }
      +    }
      +    // 下位桁から上位桁の順に走査する
      +    for exp in sequence(first: 1, next: { m >= ($0 * 10) ? $0 * 10 : nil }) {
      +        // 配列要素の k 桁目に対して計数ソートを行う
      +        // k = 1 -> exp = 1
      +        // k = 2 -> exp = 10
      +        // つまり exp = 10^(k-1)
      +        countingSortDigit(nums: &nums, exp: exp)
      +    }
      +}
       
      -
      radix_sort.js
      [class]{}-[func]{digit}
      -
      -[class]{}-[func]{countingSortDigit}
      -
      -[class]{}-[func]{radixSort}
      +
      radix_sort.js
      /* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */
      +function digit(num, exp) {
      +    // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す
      +    return Math.floor(num / exp) % 10;
      +}
      +
      +/* 計数ソート(nums の k 桁目でソート) */
      +function countingSortDigit(nums, exp) {
      +    // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要
      +    const counter = new Array(10).fill(0);
      +    const n = nums.length;
      +    // 0~9 の各数字の出現回数を集計する
      +    for (let i = 0; i < n; i++) {
      +        const d = digit(nums[i], exp); // nums[i] の第 k 位を取得し、d とする
      +        counter[d]++; // 数字 d の出現回数を数える
      +    }
      +    // 累積和を求め、「出現回数」を「配列インデックス」に変換する
      +    for (let i = 1; i < 10; i++) {
      +        counter[i] += counter[i - 1];
      +    }
      +    // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する
      +    const res = new Array(n).fill(0);
      +    for (let i = n - 1; i >= 0; i--) {
      +        const d = digit(nums[i], exp);
      +        const j = counter[d] - 1; // d の配列内インデックス j を取得する
      +        res[j] = nums[i]; // 現在の要素をインデックス j に格納する
      +        counter[d]--; // d の個数を 1 減らす
      +    }
      +    // 結果で元の配列 nums を上書きする
      +    for (let i = 0; i < n; i++) {
      +        nums[i] = res[i];
      +    }
      +}
      +
      +/* 基数ソート */
      +function radixSort(nums) {
      +    // 最大桁数の判定用に配列の最大要素を取得
      +    let m = Math.max(... nums);
      +    // 下位桁から上位桁の順に走査する
      +    for (let exp = 1; exp <= m; exp *= 10) {
      +        // 配列要素の k 桁目に対して計数ソートを行う
      +        // k = 1 -> exp = 1
      +        // k = 2 -> exp = 10
      +        // つまり exp = 10^(k-1)
      +        countingSortDigit(nums, exp);
      +    }
      +}
       
      -
      radix_sort.ts
      [class]{}-[func]{digit}
      -
      -[class]{}-[func]{countingSortDigit}
      -
      -[class]{}-[func]{radixSort}
      +
      radix_sort.ts
      /* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */
      +function digit(num: number, exp: number): number {
      +    // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す
      +    return Math.floor(num / exp) % 10;
      +}
      +
      +/* 計数ソート(nums の k 桁目でソート) */
      +function countingSortDigit(nums: number[], exp: number): void {
      +    // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要
      +    const counter = new Array(10).fill(0);
      +    const n = nums.length;
      +    // 0~9 の各数字の出現回数を集計する
      +    for (let i = 0; i < n; i++) {
      +        const d = digit(nums[i], exp); // nums[i] の第 k 位を取得し、d とする
      +        counter[d]++; // 数字 d の出現回数を数える
      +    }
      +    // 累積和を求め、「出現回数」を「配列インデックス」に変換する
      +    for (let i = 1; i < 10; i++) {
      +        counter[i] += counter[i - 1];
      +    }
      +    // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する
      +    const res = new Array(n).fill(0);
      +    for (let i = n - 1; i >= 0; i--) {
      +        const d = digit(nums[i], exp);
      +        const j = counter[d] - 1; // d の配列内インデックス j を取得する
      +        res[j] = nums[i]; // 現在の要素をインデックス j に格納する
      +        counter[d]--; // d の個数を 1 減らす
      +    }
      +    // 結果で元の配列 nums を上書きする
      +    for (let i = 0; i < n; i++) {
      +        nums[i] = res[i];
      +    }
      +}
      +
      +/* 基数ソート */
      +function radixSort(nums: number[]): void {
      +    // 最大桁数の判定用に配列の最大要素を取得
      +    let m: number = Math.max(... nums);
      +    // 下位桁から上位桁の順に走査する
      +    for (let exp = 1; exp <= m; exp *= 10) {
      +        // 配列要素の k 桁目に対して計数ソートを行う
      +        // k = 1 -> exp = 1
      +        // k = 2 -> exp = 10
      +        // つまり exp = 10^(k-1)
      +        countingSortDigit(nums, exp);
      +    }
      +}
       
      -
      radix_sort.dart
      [class]{}-[func]{digit}
      -
      -[class]{}-[func]{countingSortDigit}
      -
      -[class]{}-[func]{radixSort}
      +
      radix_sort.dart
      /* 要素 `_num` の第 k 桁を取得する。ここで `exp = 10^(k-1)` */
      +int digit(int _num, int exp) {
      +  // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す
      +  return (_num ~/ exp) % 10;
      +}
      +
      +/* 計数ソート(nums の k 桁目でソート) */
      +void countingSortDigit(List<int> nums, int exp) {
      +  // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要
      +  List<int> counter = List<int>.filled(10, 0);
      +  int n = nums.length;
      +  // 0~9 の各数字の出現回数を集計する
      +  for (int i = 0; i < n; i++) {
      +    int d = digit(nums[i], exp); // nums[i] の第 k 位を取得し、d とする
      +    counter[d]++; // 数字 d の出現回数を数える
      +  }
      +  // 累積和を求め、「出現回数」を「配列インデックス」に変換する
      +  for (int i = 1; i < 10; i++) {
      +    counter[i] += counter[i - 1];
      +  }
      +  // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する
      +  List<int> res = List<int>.filled(n, 0);
      +  for (int i = n - 1; i >= 0; i--) {
      +    int d = digit(nums[i], exp);
      +    int j = counter[d] - 1; // d の配列内インデックス j を取得する
      +    res[j] = nums[i]; // 現在の要素をインデックス j に格納する
      +    counter[d]--; // d の個数を 1 減らす
      +  }
      +  // 結果で元の配列 nums を上書きする
      +  for (int i = 0; i < n; i++) nums[i] = res[i];
      +}
      +
      +/* 基数ソート */
      +void radixSort(List<int> nums) {
      +  // 最大桁数の判定用に配列の最大要素を取得する
      +  // dart の `int` の長さは 64 ビット
      +  int m = -1 << 63;
      +  for (int _num in nums) if (_num > m) m = _num;
      +  // 下位桁から上位桁の順に走査する
      +  for (int exp = 1; exp <= m; exp *= 10)
      +    // 配列要素の k 桁目に対して計数ソートを行う
      +    // k = 1 -> exp = 1
      +    // k = 2 -> exp = 10
      +    // つまり exp = 10^(k-1)
      +    countingSortDigit(nums, exp);
      +}
       
      -
      radix_sort.rs
      [class]{}-[func]{digit}
      -
      -[class]{}-[func]{counting_sort_digit}
      -
      -[class]{}-[func]{radix_sort}
      +
      radix_sort.rs
      /* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */
      +fn digit(num: i32, exp: i32) -> usize {
      +    // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す
      +    return ((num / exp) % 10) as usize;
      +}
      +
      +/* 計数ソート(nums の k 桁目でソート) */
      +fn counting_sort_digit(nums: &mut [i32], exp: i32) {
      +    // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要
      +    let mut counter = [0; 10];
      +    let n = nums.len();
      +    // 0~9 の各数字の出現回数を集計する
      +    for i in 0..n {
      +        let d = digit(nums[i], exp); // nums[i] の第 k 位を取得し、d とする
      +        counter[d] += 1; // 数字 d の出現回数を数える
      +    }
      +    // 累積和を求め、「出現回数」を「配列インデックス」に変換する
      +    for i in 1..10 {
      +        counter[i] += counter[i - 1];
      +    }
      +    // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する
      +    let mut res = vec![0; n];
      +    for i in (0..n).rev() {
      +        let d = digit(nums[i], exp);
      +        let j = counter[d] - 1; // d の配列内インデックス j を取得する
      +        res[j] = nums[i]; // 現在の要素をインデックス j に格納する
      +        counter[d] -= 1; // d の個数を 1 減らす
      +    }
      +    // 結果で元の配列 nums を上書きする
      +    nums.copy_from_slice(&res);
      +}
      +
      +/* 基数ソート */
      +fn radix_sort(nums: &mut [i32]) {
      +    // 最大桁数の判定用に配列の最大要素を取得
      +    let m = *nums.into_iter().max().unwrap();
      +    // 下位桁から上位桁の順に走査する
      +    let mut exp = 1;
      +    while exp <= m {
      +        counting_sort_digit(nums, exp);
      +        exp *= 10;
      +    }
      +}
       
      -
      radix_sort.c
      [class]{}-[func]{digit}
      -
      -[class]{}-[func]{countingSortDigit}
      -
      -[class]{}-[func]{radixSort}
      +
      radix_sort.c
      /* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */
      +int digit(int num, int exp) {
      +    // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す
      +    return (num / exp) % 10;
      +}
      +
      +/* 計数ソート(nums の k 桁目でソート) */
      +void countingSortDigit(int nums[], int size, int exp) {
      +    // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要
      +    int *counter = (int *)malloc((sizeof(int) * 10));
      +    memset(counter, 0, sizeof(int) * 10); // 後続のメモリ解放に備えて 0 で初期化する
      +    // 0~9 の各数字の出現回数を集計する
      +    for (int i = 0; i < size; i++) {
      +        // nums[i] の第 k 位を取得し、d とする
      +        int d = digit(nums[i], exp);
      +        // 数字 d の出現回数を数える
      +        counter[d]++;
      +    }
      +    // 累積和を求め、「出現回数」を「配列インデックス」に変換する
      +    for (int i = 1; i < 10; i++) {
      +        counter[i] += counter[i - 1];
      +    }
      +    // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する
      +    int *res = (int *)malloc(sizeof(int) * size);
      +    for (int i = size - 1; i >= 0; i--) {
      +        int d = digit(nums[i], exp);
      +        int j = counter[d] - 1; // d の配列内インデックス j を取得する
      +        res[j] = nums[i];       // 現在の要素をインデックス j に格納する
      +        counter[d]--;           // d の個数を 1 減らす
      +    }
      +    // 結果で元の配列 nums を上書きする
      +    for (int i = 0; i < size; i++) {
      +        nums[i] = res[i];
      +    }
      +    // メモリを解放する
      +    free(res);
      +    free(counter);
      +}
      +
      +/* 基数ソート */
      +void radixSort(int nums[], int size) {
      +    // 最大桁数の判定用に配列の最大要素を取得
      +    int max = INT32_MIN;
      +    for (int i = 0; i < size; i++) {
      +        if (nums[i] > max) {
      +            max = nums[i];
      +        }
      +    }
      +    // 下位桁から上位桁の順に走査する
      +    for (int exp = 1; max >= exp; exp *= 10)
      +        // 配列要素の k 桁目に対して計数ソートを行う
      +        // k = 1 -> exp = 1
      +        // k = 2 -> exp = 10
      +        // つまり exp = 10^(k-1)
      +        countingSortDigit(nums, size, exp);
      +}
       
      -
      radix_sort.kt
      [class]{}-[func]{digit}
      -
      -[class]{}-[func]{countingSortDigit}
      -
      -[class]{}-[func]{radixSort}
      +
      radix_sort.kt
      /* 要素 num の下から k 桁目を取得(exp = 10^(k-1)) */
      +fun digit(num: Int, exp: Int): Int {
      +    // ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す
      +    return (num / exp) % 10
      +}
      +
      +/* 計数ソート(nums の k 桁目でソート) */
      +fun countingSortDigit(nums: IntArray, exp: Int) {
      +    // 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要
      +    val counter = IntArray(10)
      +    val n = nums.size
      +    // 0~9 の各数字の出現回数を集計する
      +    for (i in 0..<n) {
      +        val d = digit(nums[i], exp) // nums[i] の第 k 位を取得し、d とする
      +        counter[d]++                // 数字 d の出現回数を数える
      +    }
      +    // 累積和を求め、「出現回数」を「配列インデックス」に変換する
      +    for (i in 1..9) {
      +        counter[i] += counter[i - 1]
      +    }
      +    // 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する
      +    val res = IntArray(n)
      +    for (i in n - 1 downTo 0) {
      +        val d = digit(nums[i], exp)
      +        val j = counter[d] - 1 // d の配列内インデックス j を取得する
      +        res[j] = nums[i]       // 現在の要素をインデックス j に格納する
      +        counter[d]--           // d の個数を 1 減らす
      +    }
      +    // 結果で元の配列 nums を上書きする
      +    for (i in 0..<n)
      +        nums[i] = res[i]
      +}
      +
      +/* 基数ソート */
      +fun radixSort(nums: IntArray) {
      +    // 最大桁数の判定用に配列の最大要素を取得
      +    var m = Int.MIN_VALUE
      +    for (num in nums) if (num > m) m = num
      +    var exp = 1
      +    // 下位桁から上位桁の順に走査する
      +    while (exp <= m) {
      +        // 配列要素の k 桁目に対して計数ソートを行う
      +        // k = 1 -> exp = 1
      +        // k = 2 -> exp = 10
      +        // つまり exp = 10^(k-1)
      +        countingSortDigit(nums, exp)
      +        exp *= 10
      +    }
      +}
       
      -
      radix_sort.rb
      [class]{}-[func]{digit}
      -
      -[class]{}-[func]{counting_sort_digit}
      -
      -[class]{}-[func]{radix_sort}
      +
      radix_sort.rb
      ### num の第 k 桁を取得する。ここで exp = 10^(k-1) ###
      +def digit(num, exp)
      +  # k ではなく exp を渡すことで、ここで高コストな累乗計算を繰り返し実行するのを避けられる
      +  (num / exp) % 10
      +end
      +
      +### num の第 k 桁を取得する。ここで exp = 10^(k-1) ###
      +def digit(num, exp)
      +  # k ではなく exp を渡すことで、ここで高コストな累乗計算を繰り返し実行するのを避けられる
      +  (num / exp) % 10
      +end
      +
      +# ## 計数ソート(nums の k 桁目でソート)###
      +def counting_sort_digit(nums, exp)
      +  # 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要
      +  counter = Array.new(10, 0)
      +  n = nums.length
      +  # 0~9 の各数字の出現回数を集計する
      +  for i in 0...n
      +    d = digit(nums[i], exp) # nums[i] の第 k 位を取得し、d とする
      +    counter[d] += 1 # 数字 d の出現回数を数える
      +  end
      +  # 累積和を求め、「出現回数」を「配列インデックス」に変換する
      +  (1...10).each { |i| counter[i] += counter[i - 1] }
      +  # 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する
      +  res = Array.new(n, 0)
      +  for i in (n - 1).downto(0)
      +    d = digit(nums[i], exp)
      +    j = counter[d] - 1 # d の配列内インデックス j を取得する
      +    res[j] = nums[i] # 現在の要素をインデックス j に格納する
      +    counter[d] -= 1 # d の個数を 1 減らす
      +  end
      +  # 結果で元の配列 nums を上書きする
      +  (0...n).each { |i| nums[i] = res[i] }
      +end
      +
      +### 基数ソート ###
      +def radix_sort(nums)
      +  # 最大桁数の判定用に配列の最大要素を取得
      +  m = nums.max
      +  # 下位桁から上位桁の順に走査する
      +  exp = 1
      +  while exp <= m
      +    # 配列要素の k 桁目に対して計数ソートを行う
      +    # k = 1 -> exp = 1
      +    # k = 2 -> exp = 10
      +    # つまり exp = 10^(k-1)
      +    counting_sort_digit(nums, exp)
      +    exp *= 10
      +  end
      +end
       
      +
      +コードの可視化 +

      +

      +
      -

      なぜ最下位桁から開始するのか?

      -

      連続するソートラウンドでは、後のラウンドの結果が前のラウンドの結果を上書きします。例えば、最初のラウンドの結果が \(a < b\) で、2番目のラウンドが \(a > b\) の場合、2番目のラウンドの結果が最初のラウンドの結果を置き換えます。上位桁は下位桁より優先されるため、上位桁の前に下位桁をソートすることが理にかなっています。

      +

      なぜ最下位桁からソートするのですか?

      +

      連続するソートの各ラウンドでは、後のラウンドの結果が前のラウンドの結果を上書きします。たとえば、第1ラウンドで \(a < b\) となっていても、第2ラウンドで \(a > b\) となれば、第2ラウンドの結果が優先されます。数字では高位の優先度が低位より高いため、先に低位をソートし、その後で高位をソートする必要があります。

      11.10.2   アルゴリズムの特徴

      -

      計数ソートと比較して、基数ソートはより大きな数値範囲に適していますが、データが固定桁数で表現でき、桁数があまり大きくないことを前提としています。例えば、浮動小数点数は桁数 \(k\) が大きい可能性があり、時間計算量 \(O(nk) \gg O(n^2)\) につながる可能性があるため、基数ソートには適していません。

      +

      計数ソートと比べると、基数ソートは値の範囲が大きい場合に適しています。ただし、データが固定桁数の形式で表せること、かつ桁数が大きすぎないことが前提です。たとえば、浮動小数点数は基数ソートに適していません。桁数 \(k\) が大きすぎて、時間計算量が \(O(nk) \gg O(n^2)\) になる可能性があるためです。

        -
      • 時間計算量は \(O(nk)\)、非適応ソート:データサイズを \(n\)、データが基数 \(d\)、最大桁数を \(k\) とすると、単一桁のソートには \(O(n + d)\) 時間がかかり、すべての \(k\) 桁のソートには \(O((n + d)k)\) 時間がかかります。一般的に、\(d\)\(k\) はどちらも比較的小さく、時間計算量は \(O(n)\) に近づきます。
      • -
      • 空間計算量は \(O(n + d)\)、非インプレースソート:計数ソートと同様に、基数ソートは長さ \(n\)\(d\) の配列 rescounter にそれぞれ依存します。
      • -
      • 安定ソート:計数ソートが安定な場合、基数ソートも安定です。計数ソートが不安定な場合、基数ソートは正しいソート順序を保証できません。
      • +
      • 時間計算量は \(O(nk)\)、非適応ソート:データ量を \(n\)、データが \(d\) 進数、最大桁数を \(k\) とすると、ある1桁に対して計数ソートを実行する時間は \(O(n + d)\) であり、全 \(k\) 桁をソートする時間は \(O((n + d)k)\) です。通常、\(d\)\(k\) はどちらも比較的小さいため、時間計算量は \(O(n)\) に近づきます。
      • +
      • 空間計算量は \(O(n + d)\)、非原地ソート:計数ソートと同様に、基数ソートでは長さ \(n\)\(d\) の配列 rescounter を補助的に用います。
      • +
      • 安定ソート:計数ソートが安定であれば基数ソートも安定です。計数ソートが不安定な場合、基数ソートでは正しいソート結果を保証できません。
      diff --git a/ja/chapter_sorting/selection_sort/index.html b/ja/chapter_sorting/selection_sort/index.html index 4f3fb8630..6617b2b7f 100644 --- a/ja/chapter_sorting/selection_sort/index.html +++ b/ja/chapter_sorting/selection_sort/index.html @@ -6,7 +6,7 @@ - + @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1591,7 +1591,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1613,7 +1613,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1635,7 +1635,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1942,7 +1942,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2105,7 +2105,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2491,7 +2491,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2547,7 +2547,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2575,7 +2575,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -2811,7 +2811,7 @@ - 11.2.1   アルゴリズムの特性 + 11.2.1   アルゴリズムの特徴 @@ -3174,7 +3174,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3230,7 +3230,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3258,7 +3258,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3451,7 +3451,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3479,7 +3479,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3620,7 +3620,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3648,7 +3648,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3676,7 +3676,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3704,7 +3704,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3897,7 +3897,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4142,7 +4142,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4288,7 +4288,7 @@ - 11.2.1   アルゴリズムの特性 + 11.2.1   アルゴリズムの特徴 @@ -4333,19 +4333,19 @@

      11.2   選択ソート

      -

      選択ソートは非常にシンプルな原理で動作します:各反復で未ソート区間から最小要素を選択し、ソート済みセクションの末尾に移動するループを使用します。

      -

      配列の長さを\(n\)とすると、選択ソートのステップは下図に示されます。

      +

      選択ソート(selection sort)の仕組みは非常に単純です。ループを開始し、各ラウンドで未ソート区間から最小の要素を選び、整列済み区間の末尾に配置します。

      +

      配列の長さを \(n\) とすると、選択ソートの手順は次の図のようになります。

        -
      1. 最初に、すべての要素は未ソートで、つまり未ソート(インデックス)区間は\([0, n-1]\)です。
      2. -
      3. 区間\([0, n-1]\)の最小要素を選択し、インデックス\(0\)の要素と交換します。この後、配列の最初の要素がソートされます。
      4. -
      5. 区間\([1, n-1]\)の最小要素を選択し、インデックス\(1\)の要素と交換します。この後、配列の最初の2つの要素がソートされます。
      6. -
      7. この方法で続行します。\(n - 1\)ラウンドの選択と交換の後、最初の\(n - 1\)個の要素がソートされます。
      8. -
      9. 残りの唯一の要素は結果的に最大要素であり、ソートする必要がないため、配列はソートされます。
      10. +
      11. 初期状態では、すべての要素が未ソートであり、未ソートな(インデックス)区間は \([0, n-1]\) です。
      12. +
      13. 区間 \([0, n-1]\) 内の最小要素を選び、インデックス \(0\) の要素と交換します。これにより、配列の先頭 1 要素が整列済みになります。
      14. +
      15. 区間 \([1, n-1]\) 内の最小要素を選び、インデックス \(1\) の要素と交換します。これにより、配列の先頭 2 要素が整列済みになります。
      16. +
      17. これを繰り返します。\(n - 1\) 回の選択と交換を経ると、配列の先頭 \(n - 1\) 要素が整列済みになります。
      18. +
      19. 残った 1 つの要素は必ず最大要素なので、ソートは不要です。これで配列のソートは完了します。
      -

      Selection sort process

      +

      選択ソートの手順

      selection_sort_step2

      @@ -4379,23 +4379,23 @@
      -

      図 11-2   Selection sort process

      +

      図 11-2   選択ソートの手順

      -

      コードでは、\(k\)を使用して未ソート区間内の最小要素を記録します:

      +

      コードでは、\(k\) を用いて未ソート区間内の最小要素を記録します。

      selection_sort.py
      def selection_sort(nums: list[int]):
           """選択ソート"""
           n = len(nums)
      -    # 外側のループ:未ソート範囲は [i, n-1]
      +    # 外側ループ:未整列区間は [i, n-1]
           for i in range(n - 1):
      -        # 内側のループ:未ソート範囲内で最小要素を見つける
      +        # 内側のループ:未ソート区間の最小要素を見つける
               k = i
               for j in range(i + 1, n):
                   if nums[j] < nums[k]:
                       k = j  # 最小要素のインデックスを記録
      -        # 最小要素を未ソート範囲の先頭要素と交換
      +        # その最小要素を未整列区間の先頭要素と交換する
               nums[i], nums[k] = nums[k], nums[i]
       
      @@ -4403,15 +4403,15 @@
      selection_sort.cpp
      /* 選択ソート */
       void selectionSort(vector<int> &nums) {
           int n = nums.size();
      -    // 外側ループ:未ソート範囲は[i, n-1]
      +    // 外側ループ:未整列区間は [i, n-1]
           for (int i = 0; i < n - 1; i++) {
      -        // 内側ループ:未ソート範囲内で最小要素を見つける
      +        // 内側のループ:未ソート区間の最小要素を見つける
               int k = i;
               for (int j = i + 1; j < n; j++) {
                   if (nums[j] < nums[k])
                       k = j; // 最小要素のインデックスを記録
               }
      -        // 最小要素を未ソート範囲の最初の要素と交換
      +        // その最小要素を未整列区間の先頭要素と交換する
               swap(nums[i], nums[k]);
           }
       }
      @@ -4421,15 +4421,15 @@
       
      selection_sort.java
      /* 選択ソート */
       void selectionSort(int[] nums) {
           int n = nums.length;
      -    // 外側ループ: 未ソート範囲は [i, n-1]
      +    // 外側ループ:未整列区間は [i, n-1]
           for (int i = 0; i < n - 1; i++) {
      -        // 内側ループ: 未ソート範囲内で最小要素を見つける
      +        // 内側のループ:未ソート区間の最小要素を見つける
               int k = i;
               for (int j = i + 1; j < n; j++) {
                   if (nums[j] < nums[k])
                       k = j; // 最小要素のインデックスを記録
               }
      -        // 最小要素と未ソート範囲の最初の要素を交換
      +        // その最小要素を未整列区間の先頭要素と交換する
               int temp = nums[i];
               nums[i] = nums[k];
               nums[k] = temp;
      @@ -4438,55 +4438,214 @@
       
      -
      selection_sort.cs
      [class]{selection_sort}-[func]{SelectionSort}
      +
      selection_sort.cs
      /* 選択ソート */
      +void SelectionSort(int[] nums) {
      +    int n = nums.Length;
      +    // 外側ループ:未整列区間は [i, n-1]
      +    for (int i = 0; i < n - 1; i++) {
      +        // 内側のループ:未ソート区間の最小要素を見つける
      +        int k = i;
      +        for (int j = i + 1; j < n; j++) {
      +            if (nums[j] < nums[k])
      +                k = j; // 最小要素のインデックスを記録
      +        }
      +        // その最小要素を未整列区間の先頭要素と交換する
      +        (nums[k], nums[i]) = (nums[i], nums[k]);
      +    }
      +}
       
      -
      selection_sort.go
      [class]{}-[func]{selectionSort}
      +
      selection_sort.go
      /* 選択ソート */
      +func selectionSort(nums []int) {
      +    n := len(nums)
      +    // 外側ループ:未整列区間は [i, n-1]
      +    for i := 0; i < n-1; i++ {
      +        // 内側のループ:未ソート区間の最小要素を見つける
      +        k := i
      +        for j := i + 1; j < n; j++ {
      +            if nums[j] < nums[k] {
      +                // 最小要素のインデックスを記録
      +                k = j
      +            }
      +        }
      +        // その最小要素を未整列区間の先頭要素と交換する
      +        nums[i], nums[k] = nums[k], nums[i]
      +
      +    }
      +}
       
      -
      selection_sort.swift
      [class]{}-[func]{selectionSort}
      +
      selection_sort.swift
      /* 選択ソート */
      +func selectionSort(nums: inout [Int]) {
      +    // 外側ループ:未整列区間は [i, n-1]
      +    for i in nums.indices.dropLast() {
      +        // 内側のループ:未ソート区間の最小要素を見つける
      +        var k = i
      +        for j in nums.indices.dropFirst(i + 1) {
      +            if nums[j] < nums[k] {
      +                k = j // 最小要素のインデックスを記録
      +            }
      +        }
      +        // その最小要素を未整列区間の先頭要素と交換する
      +        nums.swapAt(i, k)
      +    }
      +}
       
      -
      selection_sort.js
      [class]{}-[func]{selectionSort}
      +
      selection_sort.js
      /* 選択ソート */
      +function selectionSort(nums) {
      +    let n = nums.length;
      +    // 外側ループ:未整列区間は [i, n-1]
      +    for (let i = 0; i < n - 1; i++) {
      +        // 内側のループ:未ソート区間の最小要素を見つける
      +        let k = i;
      +        for (let j = i + 1; j < n; j++) {
      +            if (nums[j] < nums[k]) {
      +                k = j; // 最小要素のインデックスを記録
      +            }
      +        }
      +        // その最小要素を未整列区間の先頭要素と交換する
      +        [nums[i], nums[k]] = [nums[k], nums[i]];
      +    }
      +}
       
      -
      selection_sort.ts
      [class]{}-[func]{selectionSort}
      +
      selection_sort.ts
      /* 選択ソート */
      +function selectionSort(nums: number[]): void {
      +    let n = nums.length;
      +    // 外側ループ:未整列区間は [i, n-1]
      +    for (let i = 0; i < n - 1; i++) {
      +        // 内側のループ:未ソート区間の最小要素を見つける
      +        let k = i;
      +        for (let j = i + 1; j < n; j++) {
      +            if (nums[j] < nums[k]) {
      +                k = j; // 最小要素のインデックスを記録
      +            }
      +        }
      +        // その最小要素を未整列区間の先頭要素と交換する
      +        [nums[i], nums[k]] = [nums[k], nums[i]];
      +    }
      +}
       
      -
      selection_sort.dart
      [class]{}-[func]{selectionSort}
      +
      selection_sort.dart
      /* 選択ソート */
      +void selectionSort(List<int> nums) {
      +  int n = nums.length;
      +  // 外側ループ:未整列区間は [i, n-1]
      +  for (int i = 0; i < n - 1; i++) {
      +    // 内側のループ:未ソート区間の最小要素を見つける
      +    int k = i;
      +    for (int j = i + 1; j < n; j++) {
      +      if (nums[j] < nums[k]) k = j; // 最小要素のインデックスを記録
      +    }
      +    // その最小要素を未整列区間の先頭要素と交換する
      +    int temp = nums[i];
      +    nums[i] = nums[k];
      +    nums[k] = temp;
      +  }
      +}
       
      -
      selection_sort.rs
      [class]{}-[func]{selection_sort}
      +
      selection_sort.rs
      /* 選択ソート */
      +fn selection_sort(nums: &mut [i32]) {
      +    if nums.is_empty() {
      +        return;
      +    }
      +    let n = nums.len();
      +    // 外側ループ:未整列区間は [i, n-1]
      +    for i in 0..n - 1 {
      +        // 内側のループ:未ソート区間の最小要素を見つける
      +        let mut k = i;
      +        for j in i + 1..n {
      +            if nums[j] < nums[k] {
      +                k = j; // 最小要素のインデックスを記録
      +            }
      +        }
      +        // その最小要素を未整列区間の先頭要素と交換する
      +        nums.swap(i, k);
      +    }
      +}
       
      -
      selection_sort.c
      [class]{}-[func]{selectionSort}
      +
      selection_sort.c
      /* 選択ソート */
      +void selectionSort(int nums[], int n) {
      +    // 外側ループ:未整列区間は [i, n-1]
      +    for (int i = 0; i < n - 1; i++) {
      +        // 内側のループ:未ソート区間の最小要素を見つける
      +        int k = i;
      +        for (int j = i + 1; j < n; j++) {
      +            if (nums[j] < nums[k])
      +                k = j; // 最小要素のインデックスを記録
      +        }
      +        // その最小要素を未整列区間の先頭要素と交換する
      +        int temp = nums[i];
      +        nums[i] = nums[k];
      +        nums[k] = temp;
      +    }
      +}
       
      -
      selection_sort.kt
      [class]{}-[func]{selectionSort}
      +
      selection_sort.kt
      /* 選択ソート */
      +fun selectionSort(nums: IntArray) {
      +    val n = nums.size
      +    // 外側ループ:未整列区間は [i, n-1]
      +    for (i in 0..<n - 1) {
      +        var k = i
      +        // 内側のループ:未ソート区間の最小要素を見つける
      +        for (j in i + 1..<n) {
      +            if (nums[j] < nums[k])
      +                k = j // 最小要素のインデックスを記録
      +        }
      +        // その最小要素を未整列区間の先頭要素と交換する
      +        val temp = nums[i]
      +        nums[i] = nums[k]
      +        nums[k] = temp
      +    }
      +}
       
      -
      selection_sort.rb
      [class]{}-[func]{selection_sort}
      +
      selection_sort.rb
      ### 選択ソート ###
      +def selection_sort(nums)
      +  n = nums.length
      +  # 外側ループ:未整列区間は [i, n-1]
      +  for i in 0...(n - 1)
      +    # 内側のループ:未ソート区間の最小要素を見つける
      +    k = i
      +    for j in (i + 1)...n
      +      if nums[j] < nums[k]
      +        k = j # 最小要素のインデックスを記録
      +      end
      +    end
      +    # その最小要素を未整列区間の先頭要素と交換する
      +    nums[i], nums[k] = nums[k], nums[i]
      +  end
      +end
       
      -

      11.2.1   アルゴリズムの特性

      +
      +コードの可視化 +

      +

      +
      +

      11.2.1   アルゴリズムの特徴

        -
      • \(O(n^2)\)の時間計算量、非適応ソート:外側ループに\(n - 1\)回の反復があり、未ソートセクションの長さは最初の反復で\(n\)から始まり、最後の反復で\(2\)まで減少します。つまり、各外側ループ反復にはそれぞれ\(n\)\(n - 1\)\(\dots\)\(3\)\(2\)回の内側ループ反復が含まれ、合計は\(\frac{(n - 1)(n + 2)}{2}\)となります。
      • -
      • \(O(1)\)の空間計算量、インプレースソート:ポインタ\(i\)\(j\)で定数の追加空間を使用します。
      • -
      • 非安定ソート:下図に示すように、要素nums[i]は等しい要素の右側に交換される可能性があり、相対順序が変わる原因となります。
      • +
      • 時間計算量は \(O(n^2)\)、非適応ソート:外側のループは合計 \(n - 1\) 回です。最初のラウンドの未ソート区間の長さは \(n\)、最後のラウンドでは \(2\) であり、各ラウンドの内側のループ回数はそれぞれ \(n\)\(n - 1\)\(\dots\)\(3\)\(2\) となります。総和は \(\frac{(n - 1)(n + 2)}{2}\) です。
      • +
      • 空間計算量は \(O(1)\)、インプレースソート:ポインタ \(i\)\(j\) は定数サイズの追加領域しか使用しません。
      • +
      • 不安定ソート:次の図のように、要素 nums[i] がそれと等しい要素の右側へ交換され、両者の相対的な順序が変わる可能性があります。
      -

      Selection sort instability example

      -

      図 11-3   Selection sort instability example

      +

      選択ソートの不安定な例

      +

      図 11-3   選択ソートの不安定な例

      diff --git a/ja/chapter_sorting/sorting_algorithm/index.html b/ja/chapter_sorting/sorting_algorithm/index.html index 67ce311d5..17ded9402 100644 --- a/ja/chapter_sorting/sorting_algorithm/index.html +++ b/ja/chapter_sorting/sorting_algorithm/index.html @@ -6,7 +6,7 @@ - + @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1591,7 +1591,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1613,7 +1613,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1635,7 +1635,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1942,7 +1942,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2105,7 +2105,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2491,7 +2491,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2547,7 +2547,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2575,7 +2575,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -2783,7 +2783,7 @@ - 11.1.1   評価次元 + 11.1.1   評価軸 @@ -3185,7 +3185,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3241,7 +3241,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3269,7 +3269,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3462,7 +3462,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3490,7 +3490,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3631,7 +3631,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3659,7 +3659,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3687,7 +3687,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3715,7 +3715,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3908,7 +3908,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4153,7 +4153,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4299,7 +4299,7 @@ - 11.1.1   評価次元 + 11.1.1   評価軸 @@ -4355,38 +4355,38 @@

      11.1   ソートアルゴリズム

      -

      ソートアルゴリズムは、データセットを特定の順序で配列するために使用されます。ソートアルゴリズムは、順序付けられたデータは通常、より効率的に探索、分析、処理できるため、幅広い応用があります。

      -

      下図に示すように、ソートアルゴリズムのデータ型は整数、浮動小数点数、文字、文字列などです。ソート基準は、数値サイズ、文字ASCII順序、またはカスタム基準など、必要に応じて設定できます。

      -

      Data types and comparator examples

      -

      図 11-1   Data types and comparator examples

      +

      ソートアルゴリズム(sorting algorithm)は、データの集合を特定の順序に従って並べ替えるために用いられます。ソートアルゴリズムは幅広く応用されており、整列済みデータは通常、より効率的に検索、分析、処理できるためです。

      +

      下図に示すように、ソートアルゴリズムにおけるデータ型は整数、浮動小数点数、文字、文字列などです。ソートの判定規則は、数値の大小、文字の ASCII コード順、またはカスタムルールなど、要件に応じて設定できます。

      +

      データ型と判定規則の例

      +

      図 11-1   データ型と判定規則の例

      -

      11.1.1   評価次元

      -

      実行効率:ソートアルゴリズムの時間計算量ができるだけ低いことを期待し、全体的な操作数も少ないこと(時間計算量の定数項を下げる)を望みます。大容量データでは、実行効率が特に重要です。

      -

      インプレース性:名前が示すとおり、インプレースソートは元の配列を直接操作することで実現され、追加のヘルパー配列が不要であるため、メモリを節約します。一般的に、インプレースソートはデータ移動操作が少なく、高速です。

      -

      安定性安定ソートは、ソート後に配列内の等しい要素の相対順序が変わらないことを保証します。

      -

      安定ソートは、マルチキーソートシナリオにおいて必要条件です。学生情報を格納するテーブルがあり、第1列と第2列がそれぞれ名前と年齢であるとします。この場合、不安定ソートは入力データの順序を失う可能性があります:

      -
      # 入力データは名前でソート済み
      -# (名前, 年齢)
      +

      11.1.1   評価軸

      +

      実行効率:ソートアルゴリズムの時間計算量はできるだけ低く、かつ全体の操作回数も少ないこと(時間計算量における定数項が小さいこと)が望まれます。大量データの場合、実行効率はとりわけ重要です。

      +

      インプレース性:その名のとおり、インプレースソートは元の配列を直接操作して並べ替えを行うため、追加の補助配列を必要とせず、メモリを節約できます。通常、インプレースソートはデータの移動操作が少なく、実行速度もより高速です。

      +

      安定性安定ソートは、並べ替え完了後も、等しい要素の配列内での相対順序が変化しません。

      +

      安定ソートは多段ソートの場面で必要条件となります。学生情報を保存した表があり、第 1 列と第 2 列がそれぞれ氏名と年齢であると仮定します。この場合、不安定ソートによって入力データの順序性が失われる可能性があります。

      +
      # 入力データは氏名順にソートされている
      +# (name, age)
         ('A', 19)
         ('B', 18)
         ('C', 21)
         ('D', 19)
         ('E', 23)
       
      -# 不安定ソートアルゴリズムを使用してリストを年齢でソートすると仮定すると、
      -# 結果は('D', 19)と('A', 19)の相対位置を変更し、
      -# 入力データが名前でソート済みであるという性質が失われる
      +# 不安定ソートアルゴリズムで年齢順にリストを並べ替えると仮定すると、
      +# 結果では ('D', 19) と ('A', 19) の相対位置が変わり、
      +# 入力データが氏名順である性質が失われる
         ('B', 18)
         ('D', 19)
         ('A', 19)
         ('C', 21)
         ('E', 23)
       
      -

      適応性適応ソートは入力データ内の既存の順序情報を活用して計算負荷を削減し、より最適な時間効率を実現します。適応ソートアルゴリズムの最良ケース時間計算量は、通常平均ケース時間計算量よりも優れています。

      -

      比較ベースまたは非比較ベース比較ベースソートは比較演算子(\(<\)\(=\)\(>\))に依存して要素の相対順序を決定し、配列全体をソートします。理論的最適時間計算量は\(O(n \log n)\)です。一方、非比較ソートは比較演算子を使用せず、\(O(n)\)の時間計算量を実現できますが、汎用性は比較的劣ります。

      +

      適応性適応的ソートは、入力データに既に存在する順序情報を利用して計算量を減らし、より優れた時間効率を実現できます。適応的ソートアルゴリズムの最良時間計算量は、通常、平均時間計算量より優れています。

      +

      比較ベースかどうか比較ベースのソートは、比較演算子(\(<\)\(=\)\(>\))に依存して要素の相対順序を判定し、それによって配列全体をソートします。理論上の最良時間計算量は \(O(n \log n)\) です。一方、非比較ソートは比較演算子を使用せず、時間計算量は \(O(n)\) に達しますが、汎用性は相対的に低くなります。

      11.1.2   理想的なソートアルゴリズム

      -

      高速実行、インプレース、安定、適応、汎用。明らかに、これらのすべての特徴を組み合わせたソートアルゴリズムは今日まで見つかっていません。したがって、ソートアルゴリズムを選択する際は、データの特定の特徴と問題の要件に基づいて決定する必要があります。

      -

      次に、さまざまなソートアルゴリズムを一緒に学び、上記の評価次元に基づいてそれぞれの利点と欠点を分析します。

      +

      高速、インプレース、安定、適応的、高い汎用性。明らかに、これまでのところ、以上のすべての特性を兼ね備えたソートアルゴリズムはまだ見つかっていません。そのため、ソートアルゴリズムを選択する際には、具体的なデータの特徴と問題の要件に応じて判断する必要があります。

      +

      次に、さまざまなソートアルゴリズムを一緒に学び、上記の評価軸に基づいて各ソートアルゴリズムの長所と短所を分析していきます。

      diff --git a/ja/chapter_sorting/summary/index.html b/ja/chapter_sorting/summary/index.html index 8397d5c31..b85fd2d24 100644 --- a/ja/chapter_sorting/summary/index.html +++ b/ja/chapter_sorting/summary/index.html @@ -6,7 +6,7 @@ - + @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1591,7 +1591,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1613,7 +1613,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1635,7 +1635,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1942,7 +1942,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2105,7 +2105,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2491,7 +2491,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2547,7 +2547,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2575,7 +2575,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -3063,7 +3063,7 @@ - 1.   重要な復習 + 1.   重要なポイントの振り返り @@ -3185,7 +3185,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3241,7 +3241,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3269,7 +3269,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3462,7 +3462,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3490,7 +3490,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3631,7 +3631,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3659,7 +3659,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3687,7 +3687,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3715,7 +3715,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3908,7 +3908,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4153,7 +4153,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4299,7 +4299,7 @@ - 1.   重要な復習 + 1.   重要なポイントの振り返り @@ -4355,37 +4355,37 @@

      11.11   まとめ

      -

      1.   重要な復習

      +

      1.   重要なポイントの振り返り

        -
      • バブルソートは隣接する要素を交換することで動作します。フラグを追加して早期リターンを可能にすることで、バブルソートの最良ケースの時間計算量を \(O(n)\) に最適化できます。
      • -
      • 挿入ソートは、未ソート区間から要素を取り出してソート済み区間の正しい位置に挿入することで各ラウンドをソートします。挿入ソートの時間計算量は \(O(n^2)\) ですが、単位あたりの操作が比較的少ないため、少量のデータのソートでは非常に人気があります。
      • -
      • クイックソートは歩哨分割操作に基づいています。歩哨分割では、常に最悪のピボットを選ぶ可能性があり、時間計算量が \(O(n^2)\) に劣化する可能性があります。中央値やランダムピボットを導入することで、そのような劣化の確率を減らすことができます。末尾再帰は再帰の深さを効果的に減らし、空間計算量を \(O(\log n)\) に最適化します。
      • -
      • マージソートには分割とマージの2つの段階があり、通常分割統治戦略を体現しています。マージソートでは、配列のソートには補助配列の作成が必要で、空間計算量は \(O(n)\) になります。しかし、リストのソートの空間計算量は \(O(1)\) に最適化できます。
      • -
      • バケットソートは3つの手順から構成されます:データをバケットに分散、各バケット内でのソート、バケット順での結果のマージ。これも分割統治戦略を体現し、非常に大きなデータセットに適しています。バケットソートの鍵はデータの均等分散です。
      • -
      • 計数ソートはバケットソートの変形で、各データポイントの出現回数をカウントすることでソートします。計数ソートは限られた範囲のデータを持つ大きなデータセットに適しており、データを正の整数に変換する必要があります。
      • -
      • 基数ソートは桁ごとにソートすることでデータを処理し、データが固定長の数値として表現される必要があります。
      • -
      • 全体的に、私たちは高効率、安定性、インプレース操作、適応性を持つソートアルゴリズムを求めています。しかし、他のデータ構造やアルゴリズムと同様に、これらすべての条件を同時に満たすソートアルゴリズムは存在しません。実際の応用では、データの特性に基づいて適切なソートアルゴリズムを選択する必要があります。
      • -
      • 以下の図は、効率性、安定性、インプレース性、適応性の観点から主流のソートアルゴリズムを比較しています。
      • +
      • バブルソートは隣接する要素を交換することで整列を行います。フラグを追加して早期リターンを可能にすると、バブルソートの最良時間計算量を \(O(n)\) に最適化できます。
      • +
      • 挿入ソートは各ラウンドで未整列区間の要素を整列済み区間の正しい位置に挿入することで整列を完了します。挿入ソートの時間計算量は \(O(n^2)\) ですが、基本操作が比較的少ないため、小規模データのソート処理で非常に人気があります。
      • +
      • クイックソートは番兵分割操作に基づいて整列を行います。番兵分割では毎回最悪の基準値を選んでしまう可能性があり、その結果、時間計算量は \(O(n^2)\) まで劣化することがあります。中央値の基準値やランダムな基準値を導入すると、この劣化の確率を下げられます。短い部分配列を優先して再帰すれば、再帰の深さを効果的に抑え、空間計算量を \(O(\log n)\) に最適化できます。
      • +
      • マージソートは分割とマージという 2 つの段階からなり、分割統治戦略を典型的に体現しています。マージソートでは配列を整列する際に補助配列の作成が必要で、空間計算量は \(O(n)\) です。一方、連結リストを整列する場合の空間計算量は \(O(1)\) まで最適化できます。
      • +
      • バケットソートはデータのバケット分配、バケット内ソート、結果の結合という 3 つの手順を含みます。これも分割統治戦略を体現しており、データ量が非常に大きい場合に適しています。バケットソートの鍵は、データを平均的に分配することにあります。
      • +
      • カウントソートはバケットソートの特例であり、データの出現回数を数えることで整列を行います。カウントソートはデータ量が大きく、かつデータ範囲が限られている場合に適しており、データを正の整数に変換できることが前提です。
      • +
      • 基数ソートは各桁ごとの整列によってデータを整列し、データが固定桁数の数値として表せることを前提とします。
      • +
      • 総じて言えば、私たちは高効率で、安定で、インプレースで、さらに適応的であるといった利点を備えたソートアルゴリズムを見つけたいと考えます。しかし、ほかのデータ構造やアルゴリズムと同様に、これらすべての条件を同時に満たせるソートアルゴリズムは存在しません。実際の応用では、データの特性に応じて適切なソートアルゴリズムを選ぶ必要があります。
      • +
      • 下図では、主流のソートアルゴリズムについて、効率、安定性、インプレース性、適応性などを比較しています。

      ソートアルゴリズムの比較

      図 11-19   ソートアルゴリズムの比較

      2.   Q & A

      -

      Q: ソートアルゴリズムの安定性はいつ必要ですか?

      -

      実際には、オブジェクトの一つの属性に基づいてソートする場合があります。例えば、学生は名前と身長の属性を持ち、多段階ソートを実装することを目指します:最初に名前で (A, 180) (B, 185) (C, 170) (D, 170) を取得し、次に身長で。ソートアルゴリズムが不安定なため、(D, 170) (C, 170) (A, 180) (B, 185) になってしまう可能性があります。

      -

      学生DとCの位置が交換され、名前の順序性が破られているのが分かります。これは望ましくありません。

      -

      Q: 歩哨分割での「右から左への検索」と「左から右への検索」の順序を交換できますか?

      -

      いいえ、最左要素をピボットとして使用する場合、最初に「右から左への検索」を行い、次に「左から右への検索」を行う必要があります。この結論はやや直観に反するので、理由を分析してみましょう。

      -

      歩哨分割 partition() の最後のステップは nums[left]nums[i] を交換することです。交換後、ピボットの左側の要素はすべてピボット以下になります。これには最後の交換前に nums[left] >= nums[i] が成り立つ必要があります。「左から右への検索」を最初に行い、ピボットより大きい要素が見つからない場合、i == j でループを終了し、nums[j] == nums[i] > nums[left] となる可能性があります。つまり、最終交換操作はピボットより大きい要素を配列の左端に交換し、歩哨分割を失敗させます。

      -

      例えば、配列 [0, 0, 0, 0, 1] が与えられた場合、最初に「左から右への検索」を行うと、歩哨分割後の配列は [1, 0, 0, 0, 0] となり、これは正しくありません。

      -

      さらに考えると、nums[right] をピボットとして選択する場合、まったく逆で、最初に「左から右への検索」を行う必要があります。

      -

      Q: 末尾再帰最適化について、短い配列を選択することで再帰の深さが \(\log n\) を超えないことを保証するのはなぜですか?

      -

      再帰の深さは現在リターンしていない再帰メソッドの数です。歩哨分割の各ラウンドは元の配列を2つの副配列に分割します。末尾再帰最適化により、再帰的に続行する副配列の長さは最大でも元の配列長の半分です。最悪の場合常に長さを半分にすると仮定すると、最終的な再帰の深さは \(\log n\) になります。

      -

      元のクイックソートを見直すと、より大きな配列を継続的に再帰処理する可能性があり、最悪の場合 \(n\)\(n - 1\)、...、\(2\)\(1\) で、再帰の深さは \(n\) になります。末尾再帰最適化はこのシナリオを回避できます。

      -

      Q: 配列のすべての要素が等しい場合、クイックソートの時間計算量は \(O(n^2)\) ですか?この劣化ケースをどう処理すべきですか?

      -

      はい。この状況については、歩哨分割を使用して配列をピボットより小さい、等しい、大きいの3つの部分に分割することを検討してください。小さい部分と大きい部分のみを再帰的に進めます。この方法では、すべての入力要素が等しい配列を1ラウンドの歩哨分割だけでソートできます。

      -

      Q: なぜバケットソートの最悪ケース時間計算量は \(O(n^2)\) ですか?

      -

      最悪の場合、すべての要素が同じバケットに配置されます。これらの要素をソートするために \(O(n^2)\) アルゴリズムを使用する場合、時間計算量は \(O(n^2)\) になります。

      +

      Q:ソートアルゴリズムの安定性は、どのような場合に必須ですか?

      +

      現実には、オブジェクトのある属性に基づいて整列することがあります。たとえば、学生には氏名と身長という 2 つの属性があり、多段階のソートを行いたいとします。まず氏名で整列して (A, 180) (B, 185) (C, 170) (D, 170) を得て、その後に身長で整列します。ソートアルゴリズムが不安定である場合、結果は (D, 170) (C, 170) (A, 180) (B, 185) になる可能性があります。

      +

      このように、学生 D と C の位置が入れ替わり、氏名に関する順序性が壊れてしまいます。これは望ましくありません。

      +

      Q:番兵分割において、「右から左へ探索する」順序と「左から右へ探索する」順序は入れ替えられますか?

      +

      できません。最も左端の要素を基準値とする場合は、必ず先に「右から左へ探索する」を行い、その後に「左から右へ探索する」を行う必要があります。この結論はやや直感に反するので、理由を分析してみましょう。

      +

      番兵分割 partition() の最後の手順は、nums[left]nums[i] を交換することです。交換が終わると、基準値の左側にある要素はすべて基準値 <= になります。したがって、最後の交換の前に nums[left] >= nums[i] が必ず成り立っていなければなりません。仮に先に「左から右へ探索する」を行うと、基準値より大きい要素が見つからない場合、i == j の時点でループを抜け、このとき nums[j] == nums[i] > nums[left] となる可能性があります。つまり、この最後の交換によって、基準値より大きい要素が配列の最左端へ移されてしまい、番兵分割は失敗します。

      +

      たとえば、配列 [0, 0, 0, 0, 1] が与えられたとき、先に「左から右へ探索する」を行うと、番兵分割後の配列は [1, 0, 0, 0, 0] になります。これは誤った結果です。

      +

      さらに考えると、nums[right] を基準値に選ぶ場合はちょうど逆になり、必ず先に「左から右へ探索する」を行う必要があります。

      +

      Q:クイックソートの再帰深度最適化について、短い配列を選ぶとなぜ再帰深度が \(\log n\) を超えないと保証できるのですか?

      +

      再帰深度とは、現在まだ戻っていない再帰呼び出しの数のことです。各ラウンドの番兵分割では、元の配列を 2 つの部分配列に分けます。再帰深度の最適化後は、下方向に再帰する部分配列の長さは最大でも元の配列長の半分です。最悪の場合でも毎回半分の長さになると仮定すれば、最終的な再帰深度は \(\log n\) になります。

      +

      元のクイックソートを振り返ると、長いほうの配列に対して連続して再帰してしまう可能性があり、最悪の場合は \(n\)\(n - 1\)\(\dots\)\(2\)\(1\) と続き、再帰深度は \(n\) になります。再帰深度の最適化により、このような状況を避けられます。

      +

      Q:配列内のすべての要素が等しい場合、クイックソートの時間計算量は \(O(n^2)\) になりますか?このような退化はどう処理すべきですか?

      +

      はい。この場合は、番兵分割によって配列を「基準値より小さい」「基準値に等しい」「基準値より大きい」の 3 つの部分に分ける方法を検討できます。下方向に再帰するのは、小さい部分と大きい部分だけです。この方法では、入力要素がすべて等しい配列は、1 回の番兵分割だけで整列を完了できます。

      +

      Q:バケットソートの最悪時間計算量が \(O(n^2)\) なのはなぜですか?

      +

      最悪の場合、すべての要素が同じバケットに振り分けられます。その要素群を整列するのに \(O(n^2)\) のアルゴリズムを使えば、時間計算量は \(O(n^2)\) になります。

      diff --git a/ja/chapter_stack_and_queue/deque/index.html b/ja/chapter_stack_and_queue/deque/index.html index b98556e41..60eb9d891 100644 --- a/ja/chapter_stack_and_queue/deque/index.html +++ b/ja/chapter_stack_and_queue/deque/index.html @@ -6,7 +6,7 @@ - + @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1559,7 +1559,7 @@ - 5.3.1   両端キューの一般的な操作 + 5.3.1   両端キューの基本操作 @@ -1702,7 +1702,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1724,7 +1724,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1746,7 +1746,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -2053,7 +2053,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2216,7 +2216,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2602,7 +2602,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2658,7 +2658,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2686,7 +2686,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -3224,7 +3224,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3280,7 +3280,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3308,7 +3308,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3501,7 +3501,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3529,7 +3529,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3670,7 +3670,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3698,7 +3698,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3726,7 +3726,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3754,7 +3754,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3947,7 +3947,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4192,7 +4192,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4338,7 +4338,7 @@ - 5.3.1   両端キューの一般的な操作 + 5.3.1   両端キューの基本操作 @@ -4433,12 +4433,12 @@

      5.3   両端キュー

      -

      キューでは、先頭からの要素の削除や末尾への要素の追加のみが可能です。下図に示すように、両端キュー(deque)はより柔軟性を提供し、先頭と末尾の両方で要素の追加や削除を可能にします。

      +

      キューでは、先頭要素を削除するか末尾に要素を追加することしかできません。次の図に示すように、両端キュー(double-ended queue)はより高い柔軟性を備えており、先頭と末尾の両方で要素の追加や削除を行えます。

      両端キューの操作

      図 5-7   両端キューの操作

      -

      5.3.1   両端キューの一般的な操作

      -

      両端キューの一般的な操作は以下の通りです。具体的なメソッド名は使用するプログラミング言語によって異なります。

      +

      5.3.1   両端キューの基本操作

      +

      両端キューの基本操作を次の表に示します。具体的なメソッド名は、使用するプログラミング言語によって異なります。

      表 5-3   両端キューの操作効率

      @@ -4452,40 +4452,40 @@ -pushFirst() +push_first() 先頭に要素を追加 \(O(1)\) -pushLast() +push_last() 末尾に要素を追加 \(O(1)\) -popFirst() +pop_first() 先頭要素を削除 \(O(1)\) -popLast() +pop_last() 末尾要素を削除 \(O(1)\) -peekFirst() +peek_first() 先頭要素にアクセス \(O(1)\) -peekLast() +peek_last() 末尾要素にアクセス \(O(1)\)
      -

      同様に、プログラミング言語で実装された両端キュークラスを直接使用することができます:

      -
      +

      同様に、プログラミング言語に組み込み実装されている両端キューのクラスを直接使うこともできます:

      +
      deque.py
      from collections import deque
      @@ -4511,7 +4511,7 @@
       # 両端キューの長さを取得
       size: int = len(deq)
       
      -# 両端キューが空かどうかを確認
      +# 両端キューが空かどうかを判定
       is_empty: bool = len(deq) == 0
       
      @@ -4537,7 +4537,7 @@ /* 両端キューの長さを取得 */ int size = deque.size(); -/* 両端キューが空かどうかを確認 */ +/* 両端キューが空かどうかを判定 */ bool empty = deque.empty();
      @@ -4563,13 +4563,13 @@ /* 両端キューの長さを取得 */ int size = deque.size(); -/* 両端キューが空かどうかを確認 */ +/* 両端キューが空かどうかを判定 */ boolean isEmpty = deque.isEmpty();
      deque.cs
      /* 両端キューを初期化 */
      -// C#では、LinkedListを両端キューとして使用
      +// C# では、連結リスト LinkedList を両端キューとして使用する
       LinkedList<int> deque = new();
       
       /* 要素をエンキュー */
      @@ -4590,13 +4590,13 @@
       /* 両端キューの長さを取得 */
       int size = deque.Count;
       
      -/* 両端キューが空かどうかを確認 */
      +/* 両端キューが空かどうかを判定 */
       bool isEmpty = deque.Count == 0;
       
      deque_test.go
      /* 両端キューを初期化 */
      -// Goでは、listを両端キューとして使用
      +// Go では、list を両端キューとして使用する
       deque := list.New()
       
       /* 要素をエンキュー */
      @@ -4617,13 +4617,13 @@
       /* 両端キューの長さを取得 */
       size := deque.Len()
       
      -/* 両端キューが空かどうかを確認 */
      +/* 両端キューが空かどうかを判定 */
       isEmpty := deque.Len() == 0
       
      deque.swift
      /* 両端キューを初期化 */
      -// Swiftには組み込みの両端キュークラスがないため、Arrayを両端キューとして使用
      +// Swift には組み込みの両端キュークラスがないため、Array を両端キューとして使用する
       var deque: [Int] = []
       
       /* 要素をエンキュー */
      @@ -4635,81 +4635,81 @@
       
       /* 要素にアクセス */
       let peekFirst = deque.first! // 先頭要素
      -let peekLast = deque.last!   // 末尾要素
      +let peekLast = deque.last! // 末尾要素
       
       /* 要素をデキュー */
      -// Arrayを使用する場合、popFirstの計算量はO(n)
      +// Array で模擬する場合、popFirst の計算量は O(n)
       let popFirst = deque.removeFirst() // 先頭要素をデキュー
      -let popLast = deque.removeLast()   // 末尾要素をデキュー
      +let popLast = deque.removeLast() // 末尾要素をデキュー
       
       /* 両端キューの長さを取得 */
       let size = deque.count
       
      -/* 両端キューが空かどうかを確認 */
      +/* 両端キューが空かどうかを判定 */
       let isEmpty = deque.isEmpty
       
      deque.js
      /* 両端キューを初期化 */
      -// JavaScriptには組み込みの両端キューがないため、Arrayを両端キューとして使用
      +// JavaScript には組み込みの両端キューがないため、Array を両端キューとして使用するしかない
       const deque = [];
       
       /* 要素をエンキュー */
       deque.push(2);
       deque.push(5);
       deque.push(4);
      -// 注意:unshift()は配列のため時間計算量がO(n)
      +// 配列であるため、unshift() メソッドの時間計算量は O(n) です
       deque.unshift(3);
       deque.unshift(1);
       
       /* 要素にアクセス */
      -const peekFirst = deque[0]; // 先頭要素
      -const peekLast = deque[deque.length - 1]; // 末尾要素
      +const peekFirst = deque[0];
      +const peekLast = deque[deque.length - 1];
       
       /* 要素をデキュー */
      -// 注意:shift()は配列のため時間計算量がO(n)
      -const popFront = deque.shift(); // 先頭要素をデキュー
      -const popBack = deque.pop();    // 末尾要素をデキュー
      +// 配列であるため、shift() メソッドの時間計算量は O(n) です
      +const popFront = deque.shift();
      +const popBack = deque.pop();
       
       /* 両端キューの長さを取得 */
       const size = deque.length;
       
      -/* 両端キューが空かどうかを確認 */
      +/* 両端キューが空かどうかを判定 */
       const isEmpty = size === 0;
       
      deque.ts
      /* 両端キューを初期化 */
      -// TypeScriptには組み込みの両端キューがないため、Arrayを両端キューとして使用
      +// TypeScript には組み込みの両端キューがないため、Array を両端キューとして使用するしかない
       const deque: number[] = [];
       
       /* 要素をエンキュー */
       deque.push(2);
       deque.push(5);
       deque.push(4);
      -// 注意:unshift()は配列のため時間計算量がO(n)
      +// 配列であるため、unshift() メソッドの時間計算量は O(n) です
       deque.unshift(3);
       deque.unshift(1);
       
       /* 要素にアクセス */
      -const peekFirst: number = deque[0]; // 先頭要素
      -const peekLast: number = deque[deque.length - 1]; // 末尾要素
      +const peekFirst: number = deque[0];
      +const peekLast: number = deque[deque.length - 1];
       
       /* 要素をデキュー */
      -// 注意:shift()は配列のため時間計算量がO(n)
      -const popFront: number = deque.shift() as number; // 先頭要素をデキュー
      -const popBack: number = deque.pop() as number;    // 末尾要素をデキュー
      +// 配列であるため、shift() メソッドの時間計算量は O(n) です
      +const popFront: number = deque.shift() as number;
      +const popBack: number = deque.pop() as number;
       
       /* 両端キューの長さを取得 */
       const size: number = deque.length;
       
      -/* 両端キューが空かどうかを確認 */
      +/* 両端キューが空かどうかを判定 */
       const isEmpty: boolean = size === 0;
       
      deque.dart
      /* 両端キューを初期化 */
      -// Dartでは、Queueが両端キューとして定義される
      +// Dart では、Queue は両端キューとして定義されています
       Queue<int> deque = Queue<int>();
       
       /* 要素をエンキュー */
      @@ -4730,7 +4730,7 @@
       /* 両端キューの長さを取得 */
       int size = deque.length;
       
      -/* 両端キューが空かどうかを確認 */
      +/* 両端キューが空かどうかを判定 */
       bool isEmpty = deque.isEmpty;
       
      @@ -4760,30 +4760,85 @@ /* 両端キューの長さを取得 */ let size = deque.len(); -/* 両端キューが空かどうかを確認 */ +/* 両端キューが空かどうかを判定 */ let is_empty = deque.is_empty();
      -
      deque.c
      // Cには組み込みの両端キューが提供されていません
      +
      deque.c
      // C には組み込みの両端キューがありません
       
      -
      deque.kt
      
      +
      deque.kt
      /* 両端キューを初期化 */
      +val deque = LinkedList<Int>()
      +
      +/* 要素をエンキュー */
      +deque.offerLast(2)  // 末尾に追加
      +deque.offerLast(5)
      +deque.offerLast(4)
      +deque.offerFirst(3) // 先頭に追加
      +deque.offerFirst(1)
      +
      +/* 要素にアクセス */
      +val peekFirst = deque.peekFirst() // 先頭要素
      +val peekLast = deque.peekLast()   // 末尾要素
      +
      +/* 要素をデキュー */
      +val popFirst = deque.pollFirst() // 先頭要素をデキュー
      +val popLast = deque.pollLast()   // 末尾要素をデキュー
      +
      +/* 両端キューの長さを取得 */
      +val size = deque.size
      +
      +/* 両端キューが空かどうかを判定 */
      +val isEmpty = deque.isEmpty()
      +
      +
      +
      +
      deque.rb
      # 両端キューを初期化
      +# Ruby には組み込みの両端キューがないため、Array を両端キューとして使用するしかありません
      +deque = []
      +
      +# 要素をエンキュー
      +deque << 2
      +deque << 5
      +deque << 4
      +# 配列であるため、Array#unshift メソッドの時間計算量は O(n) です
      +deque.unshift(3)
      +deque.unshift(1)
      +
      +# 要素にアクセス
      +peek_first = deque.first
      +peek_last = deque.last
      +
      +# 要素をデキュー
      +# 配列であるため、 Array#shift メソッドの時間計算量は O(n) です
      +pop_front = deque.shift
      +pop_back = deque.pop
      +
      +# 両端キューの長さを取得
      +size = deque.length
      +
      +# 両端キューが空かどうかを判定
      +is_empty = size.zero?
       
      +
      +実行の可視化 +

      https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%0A%20%20%20%20deq%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20deq.append%282%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E5%B0%BE%0A%20%20%20%20deq.append%285%29%0A%20%20%20%20deq.append%284%29%0A%20%20%20%20deq.appendleft%283%29%20%20%23%20%E6%B7%BB%E5%8A%A0%E8%87%B3%E9%98%9F%E9%A6%96%0A%20%20%20%20deq.appendleft%281%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20deq%5B0%5D%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%20%20%20%20rear%20%3D%20deq%5B-1%5D%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%20rear%20%3D%22,%20rear%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop_front%20%3D%20deq.popleft%28%29%20%20%23%20%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_front%20%3D%22,%20pop_front%29%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%20%20%20%20pop_rear%20%3D%20deq.pop%28%29%20%20%23%20%E9%98%9F%E5%B0%BE%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20%20pop_rear%20%3D%22,%20pop_rear%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%B0%BE%E5%87%BA%E9%98%9F%E5%90%8E%20deque%20%3D%22,%20deq%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28deq%29%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28deq%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false

      +

      5.3.2   両端キューの実装 *

      -

      両端キューの実装は通常のキューの実装と似ており、連結リストまたは配列を基盤となるデータ構造として使用できます。

      +

      両端キューの実装はキューと似ており、連結リストまたは配列を基盤となるデータ構造として選べます。

      1.   双方向連結リストに基づく実装

      -

      前節で、通常の単一連結リストを使ってキューを実装したことを思い出してください。これは先頭からの削除(デキュー操作に対応)と末尾への新しい要素の追加(エンキュー操作に対応)を便利に行えるためでした。

      -

      両端キューでは、先頭と末尾の両方でエンキューとデキュー操作を実行できます。つまり、両端キューは逆方向の操作も実装する必要があります。このため、両端キューの基盤となるデータ構造として「双方向連結リスト」を使用します。

      -

      下図に示すように、双方向連結リストの先頭ノードと末尾ノードをそれぞれ両端キューの前端と後端として扱い、両端でのノードの追加と削除機能を実装します。

      -
      +

      前節を振り返ると、通常の単方向連結リストを使ってキューを実装しました。これは、先頭ノードの削除(デキューに対応)と末尾ノードの後ろへの新規ノード追加(エンキューに対応)を容易に行えるためです。

      +

      両端キューでは、先頭と末尾のどちらでもエンキューとデキューを行えます。言い換えると、両端キューではもう一方の対称方向の操作も実装する必要があります。そのため、両端キューの基盤データ構造として「双方向連結リスト」を採用します。

      +

      次の図に示すように、双方向連結リストの先頭ノードと末尾ノードを両端キューの先頭と末尾と見なし、両端でノードを追加および削除する機能を実現します。

      +
      -

      双方向連結リストによる両端キューのエンキューとデキュー操作の実装

      +

      連結リストによる両端キューのエンキューとデキュー

      linkedlist_deque_push_last

      @@ -4799,461 +4854,1718 @@
      -

      図 5-8   双方向連結リストによる両端キューのエンキューとデキュー操作の実装

      +

      図 5-8   連結リストによる両端キューのエンキューとデキュー

      -

      実装コードは以下の通りです:

      +

      実装コードは次のとおりです:

      -
      linkedlist_deque.py
      class ListNode:
      -    """双方向連結リストノード"""
      -
      -    def __init__(self, val: int):
      -        """コンストラクタ"""
      -        self.val: int = val
      -        self.next: ListNode | None = None  # 後続ノードへの参照
      -        self.prev: ListNode | None = None  # 前駆ノードへの参照
      -
      -class LinkedListDeque:
      -    """双方向連結リストベースの双端キュークラス"""
      -
      -    def __init__(self):
      -        """コンストラクタ"""
      -        self._front: ListNode | None = None  # ヘッドノード front
      -        self._rear: ListNode | None = None  # テールノード rear
      -        self._size: int = 0  # 双端キューの長さ
      -
      -    def size(self) -> int:
      -        """双端キューの長さを取得"""
      -        return self._size
      -
      -    def is_empty(self) -> bool:
      -        """双端キューが空かどうかを判定"""
      -        return self._size == 0
      -
      -    def push(self, num: int, is_front: bool):
      -        """エンキュー操作"""
      -        node = ListNode(num)
      -        # リストが空の場合、front と rear の両方を node に向ける
      -        if self.is_empty():
      -            self._front = self._rear = node
      -        # 前端エンキュー操作
      -        elif is_front:
      -            # ノードをリストの先頭に追加
      -            self._front.prev = node
      -            node.next = self._front
      -            self._front = node  # ヘッドノードを更新
      -        # 後端エンキュー操作
      -        else:
      -            # ノードをリストの末尾に追加
      -            self._rear.next = node
      -            node.prev = self._rear
      -            self._rear = node  # テールノードを更新
      -        self._size += 1  # キューの長さを更新
      -
      -    def push_first(self, num: int):
      -        """前端エンキュー"""
      -        self.push(num, True)
      -
      -    def push_last(self, num: int):
      -        """後端エンキュー"""
      -        self.push(num, False)
      -
      -    def pop(self, is_front: bool) -> int:
      -        """デキュー操作"""
      -        if self.is_empty():
      -            raise IndexError("Double-ended queue is empty")
      -        # 前端デキュー操作
      -        if is_front:
      -            val: int = self._front.val  # ヘッドノードの値を一時的に保存
      -            # ヘッドノードを削除
      -            fnext: ListNode | None = self._front.next
      -            if fnext is not None:
      -                fnext.prev = None
      -                self._front.next = None
      -            self._front = fnext  # ヘッドノードを更新
      -        # 後端デキュー操作
      -        else:
      -            val: int = self._rear.val  # テールノードの値を一時的に保存
      -            # テールノードを削除
      -            rprev: ListNode | None = self._rear.prev
      -            if rprev is not None:
      -                rprev.next = None
      -                self._rear.prev = None
      -            self._rear = rprev  # テールノードを更新
      -        self._size -= 1  # キューの長さを更新
      -        return val
      -
      -    def pop_first(self) -> int:
      -        """前端デキュー"""
      -        return self.pop(True)
      -
      -    def pop_last(self) -> int:
      -        """後端デキュー"""
      -        return self.pop(False)
      -
      -    def peek_first(self) -> int:
      -        """前端要素にアクセス"""
      -        if self.is_empty():
      -            raise IndexError("Double-ended queue is empty")
      -        return self._front.val
      -
      -    def peek_last(self) -> int:
      -        """後端要素にアクセス"""
      -        if self.is_empty():
      -            raise IndexError("Double-ended queue is empty")
      -        return self._rear.val
      -
      -    def to_array(self) -> list[int]:
      -        """出力用の配列を返す"""
      -        node = self._front
      -        res = [0] * self.size()
      -        for i in range(self.size()):
      -            res[i] = node.val
      -            node = node.next
      -        return res
      -
      -
      -
      -
      linkedlist_deque.cpp
      /* 双方向連結リストノード */
      -struct DoublyListNode {
      -    int val;              // ノードの値
      -    DoublyListNode *next; // 後続ノードへのポインタ
      -    DoublyListNode *prev; // 前続ノードへのポインタ
      -    DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) {
      -    }
      -};
      +
      linkedlist_deque.py
      class ListNode:
      +    """双方向連結リストノード"""
      +
      +    def __init__(self, val: int):
      +        """コンストラクタ"""
      +        self.val: int = val
      +        self.next: ListNode | None = None  # 後続ノードへの参照
      +        self.prev: ListNode | None = None  # 前駆ノードへの参照
       
      -/* 双方向連結リストに基づく両端キュークラス */
      -class LinkedListDeque {
      -  private:
      -    DoublyListNode *front, *rear; // 先頭ノードfront、末尾ノードrear
      -    int queSize = 0;              // 両端キューの長さ
      -
      -  public:
      -    /* コンストラクタ */
      -    LinkedListDeque() : front(nullptr), rear(nullptr) {
      -    }
      -
      -    /* デストラクタ */
      -    ~LinkedListDeque() {
      -        // 連結リストを走査、ノードを削除、メモリを解放
      -        DoublyListNode *pre, *cur = front;
      -        while (cur != nullptr) {
      -            pre = cur;
      -            cur = cur->next;
      -            delete pre;
      -        }
      -    }
      -
      -    /* 両端キューの長さを取得 */
      -    int size() {
      -        return queSize;
      -    }
      -
      -    /* 両端キューが空かどうかを判定 */
      -    bool isEmpty() {
      -        return size() == 0;
      -    }
      -
      -    /* エンキュー操作 */
      -    void push(int num, bool isFront) {
      -        DoublyListNode *node = new DoublyListNode(num);
      -        // リストが空の場合、frontとrearの両方をnodeに向ける
      -        if (isEmpty())
      -            front = rear = node;
      -        // 先頭エンキュー操作
      -        else if (isFront) {
      -            // ノードをリストの先頭に追加
      -            front->prev = node;
      -            node->next = front;
      -            front = node; // 先頭ノードを更新
      -        // 末尾エンキュー操作
      -        } else {
      -            // ノードをリストの末尾に追加
      -            rear->next = node;
      -            node->prev = rear;
      -            rear = node; // 末尾ノードを更新
      -        }
      -        queSize++; // キュー長を更新
      -    }
      -
      -    /* 先頭エンキュー */
      -    void pushFirst(int num) {
      -        push(num, true);
      -    }
      -
      -    /* 末尾エンキュー */
      -    void pushLast(int num) {
      -        push(num, false);
      -    }
      -
      -    /* デキュー操作 */
      -    int pop(bool isFront) {
      -        if (isEmpty())
      -            throw out_of_range("Queue is empty");
      -        int val;
      -        // 先頭デキュー操作
      -        if (isFront) {
      -            val = front->val; // 先頭ノードの値を一時保存
      -            // 先頭ノードを削除
      -            DoublyListNode *fNext = front->next;
      -            if (fNext != nullptr) {
      -                fNext->prev = nullptr;
      -                front->next = nullptr;
      -            }
      -            delete front;
      -            front = fNext; // 先頭ノードを更新
      -        // 末尾デキュー操作
      -        } else {
      -            val = rear->val; // 末尾ノードの値を一時保存
      -            // 末尾ノードを削除
      -            DoublyListNode *rPrev = rear->prev;
      -            if (rPrev != nullptr) {
      -                rPrev->next = nullptr;
      -                rear->prev = nullptr;
      -            }
      -            delete rear;
      -            rear = rPrev; // 末尾ノードを更新
      -        }
      -        queSize--; // キュー長を更新
      -        return val;
      -    }
      -
      -    /* 先頭デキュー */
      -    int popFirst() {
      -        return pop(true);
      -    }
      -
      -    /* 末尾デキュー */
      -    int popLast() {
      -        return pop(false);
      -    }
      -
      -    /* 先頭要素にアクセス */
      -    int peekFirst() {
      -        if (isEmpty())
      -            throw out_of_range("Double-ended queue is empty");
      -        return front->val;
      -    }
      -
      -    /* 末尾要素にアクセス */
      -    int peekLast() {
      -        if (isEmpty())
      -            throw out_of_range("Double-ended queue is empty");
      -        return rear->val;
      -    }
      -
      -    /* 印刷用に配列を返却 */
      -    vector<int> toVector() {
      -        DoublyListNode *node = front;
      -        vector<int> res(size());
      -        for (int i = 0; i < res.size(); i++) {
      -            res[i] = node->val;
      -            node = node->next;
      -        }
      -        return res;
      -    }
      -};
      +class LinkedListDeque:
      +    """双方向連結リストベースの両端キュー"""
      +
      +    def __init__(self):
      +        """コンストラクタ"""
      +        self._front: ListNode | None = None  # 先頭ノード front
      +        self._rear: ListNode | None = None  # 末尾ノード rear
      +        self._size: int = 0  # 両端キューの長さ
      +
      +    def size(self) -> int:
      +        """両端キューの長さを取得"""
      +        return self._size
      +
      +    def is_empty(self) -> bool:
      +        """両端キューが空かどうかを判定"""
      +        return self._size == 0
      +
      +    def push(self, num: int, is_front: bool):
      +        """エンキュー操作"""
      +        node = ListNode(num)
      +        # 連結リストが空なら、front と rear の両方を node に向ける
      +        if self.is_empty():
      +            self._front = self._rear = node
      +        # 先頭へのエンキュー操作
      +        elif is_front:
      +            # node を連結リストの先頭に追加
      +            self._front.prev = node
      +            node.next = self._front
      +            self._front = node  # 先頭ノードを更新する
      +        # 末尾へのエンキュー操作
      +        else:
      +            # node を連結リストの末尾に追加
      +            self._rear.next = node
      +            node.prev = self._rear
      +            self._rear = node  # 末尾ノードを更新する
      +        self._size += 1  # キューの長さを更新
      +
      +    def push_first(self, num: int):
      +        """キュー先頭にエンキュー"""
      +        self.push(num, True)
      +
      +    def push_last(self, num: int):
      +        """キュー末尾にエンキュー"""
      +        self.push(num, False)
      +
      +    def pop(self, is_front: bool) -> int:
      +        """デキュー操作"""
      +        if self.is_empty():
      +            raise IndexError("両端キューが空です")
      +        # キュー先頭からの取り出し
      +        if is_front:
      +            val: int = self._front.val  # 先頭ノードの値を一時保存
      +            # 先頭ノードを削除
      +            fnext: ListNode | None = self._front.next
      +            if fnext is not None:
      +                fnext.prev = None
      +                self._front.next = None
      +            self._front = fnext  # 先頭ノードを更新する
      +        # キュー末尾からの取り出し
      +        else:
      +            val: int = self._rear.val  # 末尾ノードの値を一時保存
      +            # 末尾ノードを削除
      +            rprev: ListNode | None = self._rear.prev
      +            if rprev is not None:
      +                rprev.next = None
      +                self._rear.prev = None
      +            self._rear = rprev  # 末尾ノードを更新する
      +        self._size -= 1  # キューの長さを更新
      +        return val
      +
      +    def pop_first(self) -> int:
      +        """キュー先頭からデキュー"""
      +        return self.pop(True)
      +
      +    def pop_last(self) -> int:
      +        """キュー末尾からデキュー"""
      +        return self.pop(False)
      +
      +    def peek_first(self) -> int:
      +        """キュー先頭の要素にアクセス"""
      +        if self.is_empty():
      +            raise IndexError("両端キューが空です")
      +        return self._front.val
      +
      +    def peek_last(self) -> int:
      +        """キュー末尾の要素にアクセス"""
      +        if self.is_empty():
      +            raise IndexError("両端キューが空です")
      +        return self._rear.val
      +
      +    def to_array(self) -> list[int]:
      +        """出力用の配列を返す"""
      +        node = self._front
      +        res = [0] * self.size()
      +        for i in range(self.size()):
      +            res[i] = node.val
      +            node = node.next
      +        return res
       
      -
      linkedlist_deque.java
      /* 双方向連結リストノード */
      -class ListNode {
      -    int val; // ノード値
      -    ListNode next; // 後続ノードへの参照
      -    ListNode prev; // 前任ノードへの参照
      -
      -    ListNode(int val) {
      -        this.val = val;
      -        prev = next = null;
      -    }
      -}
      -
      -/* 双方向連結リストに基づく両端キュークラス */
      -class LinkedListDeque {
      -    private ListNode front, rear; // 先頭ノード front、末尾ノード rear
      -    private int queSize = 0; // 両端キューの長さ
      -
      -    public LinkedListDeque() {
      -        front = rear = null;
      -    }
      -
      -    /* 両端キューの長さを取得 */
      -    public int size() {
      -        return queSize;
      -    }
      -
      -    /* 両端キューが空かどうかを判定 */
      -    public boolean isEmpty() {
      -        return size() == 0;
      +
      linkedlist_deque.cpp
      /* 双方向連結リストノード */
      +struct DoublyListNode {
      +    int val;              // ノード値
      +    DoublyListNode *next; // 後継ノードへのポインタ
      +    DoublyListNode *prev; // 前駆ノードへのポインタ
      +    DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) {
      +    }
      +};
      +
      +/* 双方向連結リストベースの両端キュー */
      +class LinkedListDeque {
      +  private:
      +    DoublyListNode *front, *rear; // 先頭ノード front、末尾ノード rear
      +    int queSize = 0;              // 両端キューの長さ
      +
      +  public:
      +    /* コンストラクタ */
      +    LinkedListDeque() : front(nullptr), rear(nullptr) {
      +    }
      +
      +    /* デストラクタメソッド */
      +    ~LinkedListDeque() {
      +        // 連結リストを走査してノードを削除し、メモリを解放する
      +        DoublyListNode *pre, *cur = front;
      +        while (cur != nullptr) {
      +            pre = cur;
      +            cur = cur->next;
      +            delete pre;
      +        }
           }
       
      -    /* エンキュー操作 */
      -    private void push(int num, boolean isFront) {
      -        ListNode node = new ListNode(num);
      -        // リストが空の場合、front と rear の両方を node に指す
      -        if (isEmpty())
      -            front = rear = node;
      -        // 先頭エンキュー操作
      -        else if (isFront) {
      -            // node をリストの先頭に追加
      -            front.prev = node;
      -            node.next = front;
      -            front = node; // front を更新
      -        // 末尾エンキュー操作
      -        } else {
      -            // node をリストの末尾に追加
      -            rear.next = node;
      -            node.prev = rear;
      -            rear = node; // rear を更新
      -        }
      -        queSize++; // 長さを更新
      -    }
      -
      -    /* 先頭エンキュー */
      -    public void pushFirst(int num) {
      -        push(num, true);
      -    }
      -
      -    /* 末尾エンキュー */
      -    public void pushLast(int num) {
      -        push(num, false);
      +    /* 両端キューの長さを取得 */
      +    int size() {
      +        return queSize;
      +    }
      +
      +    /* 両端キューが空かどうかを判定 */
      +    bool isEmpty() {
      +        return size() == 0;
      +    }
      +
      +    /* エンキュー操作 */
      +    void push(int num, bool isFront) {
      +        DoublyListNode *node = new DoublyListNode(num);
      +        // 連結リストが空なら、front と rear の両方を node に向ける
      +        if (isEmpty())
      +            front = rear = node;
      +        // 先頭へのエンキュー操作
      +        else if (isFront) {
      +            // node を連結リストの先頭に追加
      +            front->prev = node;
      +            node->next = front;
      +            front = node; // 先頭ノードを更新する
      +        // 末尾へのエンキュー操作
      +        } else {
      +            // node を連結リストの末尾に追加
      +            rear->next = node;
      +            node->prev = rear;
      +            rear = node; // 末尾ノードを更新する
      +        }
      +        queSize++; // キューの長さを更新
           }
       
      -    /* デキュー操作 */
      -    private int pop(boolean isFront) {
      -        if (isEmpty())
      -            throw new IndexOutOfBoundsException();
      -        int val;
      -        // 先頭デキュー操作
      -        if (isFront) {
      -            val = front.val; // 一時的に先頭ノード値を保存
      -            // 次のノードを削除
      -            ListNode fNext = front.next;
      -            if (fNext != null) {
      -                fNext.prev = null;
      -                front.next = null;
      -            }
      -            front = fNext; // front を更新
      -        // 末尾デキュー操作
      -        } else {
      -            val = rear.val; // 一時的に末尾ノード値を保存
      -            // 前のノードを削除
      -            ListNode rPrev = rear.prev;
      -            if (rPrev != null) {
      -                rPrev.next = null;
      -                rear.prev = null;
      +    /* キュー先頭にエンキュー */
      +    void pushFirst(int num) {
      +        push(num, true);
      +    }
      +
      +    /* キュー末尾にエンキュー */
      +    void pushLast(int num) {
      +        push(num, false);
      +    }
      +
      +    /* デキュー操作 */
      +    int pop(bool isFront) {
      +        if (isEmpty())
      +            throw out_of_range("キューが空です");
      +        int val;
      +        // キュー先頭からの取り出し
      +        if (isFront) {
      +            val = front->val; // 先頭ノードの値を一時保存
      +            // 先頭ノードを削除
      +            DoublyListNode *fNext = front->next;
      +            if (fNext != nullptr) {
      +                fNext->prev = nullptr;
      +                front->next = nullptr;
                   }
      -            rear = rPrev; // rear を更新
      -        }
      -        queSize--; // 長さを更新
      -        return val;
      -    }
      -
      -    /* 先頭デキュー */
      -    public int popFirst() {
      -        return pop(true);
      -    }
      -
      -    /* 末尾デキュー */
      -    public int popLast() {
      -        return pop(false);
      -    }
      -
      -    /* 先頭要素にアクセス */
      -    public int peekFirst() {
      -        if (isEmpty())
      -            throw new IndexOutOfBoundsException();
      -        return front.val;
      +            delete front;
      +            front = fNext; // 先頭ノードを更新する
      +        // キュー末尾からの取り出し
      +        } else {
      +            val = rear->val; // 末尾ノードの値を一時保存
      +            // 末尾ノードを削除
      +            DoublyListNode *rPrev = rear->prev;
      +            if (rPrev != nullptr) {
      +                rPrev->next = nullptr;
      +                rear->prev = nullptr;
      +            }
      +            delete rear;
      +            rear = rPrev; // 末尾ノードを更新する
      +        }
      +        queSize--; // キューの長さを更新
      +        return val;
      +    }
      +
      +    /* キュー先頭からデキュー */
      +    int popFirst() {
      +        return pop(true);
           }
       
      -    /* 末尾要素にアクセス */
      -    public int peekLast() {
      -        if (isEmpty())
      -            throw new IndexOutOfBoundsException();
      -        return rear.val;
      -    }
      -
      -    /* 配列を返す */
      -    public int[] toArray() {
      -        ListNode node = front;
      -        int[] res = new int[size()];
      -        for (int i = 0; i < res.length; i++) {
      -            res[i] = node.val;
      -            node = node.next;
      -        }
      -        return res;
      -    }
      -}
      +    /* キュー末尾からデキュー */
      +    int popLast() {
      +        return pop(false);
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    int peekFirst() {
      +        if (isEmpty())
      +            throw out_of_range("両端キューが空です");
      +        return front->val;
      +    }
      +
      +    /* キュー末尾の要素にアクセス */
      +    int peekLast() {
      +        if (isEmpty())
      +            throw out_of_range("両端キューが空です");
      +        return rear->val;
      +    }
      +
      +    /* 出力用の配列を返す */
      +    vector<int> toVector() {
      +        DoublyListNode *node = front;
      +        vector<int> res(size());
      +        for (int i = 0; i < res.size(); i++) {
      +            res[i] = node->val;
      +            node = node->next;
      +        }
      +        return res;
      +    }
      +};
       
      -
      linkedlist_deque.cs
      [class]{ListNode}-[func]{}
      -
      -[class]{LinkedListDeque}-[func]{}
      +
      linkedlist_deque.java
      /* 双方向連結リストノード */
      +class ListNode {
      +    int val; // ノード値
      +    ListNode next; // 後続ノードへの参照
      +    ListNode prev; // 前駆ノードへの参照
      +
      +    ListNode(int val) {
      +        this.val = val;
      +        prev = next = null;
      +    }
      +}
      +
      +/* 双方向連結リストベースの両端キュー */
      +class LinkedListDeque {
      +    private ListNode front, rear; // 先頭ノード front、末尾ノード rear
      +    private int queSize = 0; // 両端キューの長さ
      +
      +    public LinkedListDeque() {
      +        front = rear = null;
      +    }
      +
      +    /* 両端キューの長さを取得 */
      +    public int size() {
      +        return queSize;
      +    }
      +
      +    /* 両端キューが空かどうかを判定 */
      +    public boolean isEmpty() {
      +        return size() == 0;
      +    }
      +
      +    /* エンキュー操作 */
      +    private void push(int num, boolean isFront) {
      +        ListNode node = new ListNode(num);
      +        // 連結リストが空なら、front と rear の両方を node に向ける
      +        if (isEmpty())
      +            front = rear = node;
      +        // 先頭へのエンキュー操作
      +        else if (isFront) {
      +            // node を連結リストの先頭に追加
      +            front.prev = node;
      +            node.next = front;
      +            front = node; // 先頭ノードを更新する
      +        // 末尾へのエンキュー操作
      +        } else {
      +            // node を連結リストの末尾に追加
      +            rear.next = node;
      +            node.prev = rear;
      +            rear = node; // 末尾ノードを更新する
      +        }
      +        queSize++; // キューの長さを更新
      +    }
      +
      +    /* キュー先頭にエンキュー */
      +    public void pushFirst(int num) {
      +        push(num, true);
      +    }
      +
      +    /* キュー末尾にエンキュー */
      +    public void pushLast(int num) {
      +        push(num, false);
      +    }
      +
      +    /* デキュー操作 */
      +    private int pop(boolean isFront) {
      +        if (isEmpty())
      +            throw new IndexOutOfBoundsException();
      +        int val;
      +        // キュー先頭からの取り出し
      +        if (isFront) {
      +            val = front.val; // 先頭ノードの値を一時保存
      +            // 先頭ノードを削除
      +            ListNode fNext = front.next;
      +            if (fNext != null) {
      +                fNext.prev = null;
      +                front.next = null;
      +            }
      +            front = fNext; // 先頭ノードを更新する
      +        // キュー末尾からの取り出し
      +        } else {
      +            val = rear.val; // 末尾ノードの値を一時保存
      +            // 末尾ノードを削除
      +            ListNode rPrev = rear.prev;
      +            if (rPrev != null) {
      +                rPrev.next = null;
      +                rear.prev = null;
      +            }
      +            rear = rPrev; // 末尾ノードを更新する
      +        }
      +        queSize--; // キューの長さを更新
      +        return val;
      +    }
      +
      +    /* キュー先頭からデキュー */
      +    public int popFirst() {
      +        return pop(true);
      +    }
      +
      +    /* キュー末尾からデキュー */
      +    public int popLast() {
      +        return pop(false);
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    public int peekFirst() {
      +        if (isEmpty())
      +            throw new IndexOutOfBoundsException();
      +        return front.val;
      +    }
      +
      +    /* キュー末尾の要素にアクセス */
      +    public int peekLast() {
      +        if (isEmpty())
      +            throw new IndexOutOfBoundsException();
      +        return rear.val;
      +    }
      +
      +    /* 出力用の配列を返す */
      +    public int[] toArray() {
      +        ListNode node = front;
      +        int[] res = new int[size()];
      +        for (int i = 0; i < res.length; i++) {
      +            res[i] = node.val;
      +            node = node.next;
      +        }
      +        return res;
      +    }
      +}
       
      -
      linkedlist_deque.go
      [class]{linkedListDeque}-[func]{}
      +
      linkedlist_deque.cs
      /* 双方向連結リストノード */
      +class ListNode(int val) {
      +    public int val = val;       // ノード値
      +    public ListNode? next = null; // 後続ノードへの参照
      +    public ListNode? prev = null; // 前駆ノードへの参照
      +}
      +
      +/* 双方向連結リストベースの両端キュー */
      +class LinkedListDeque {
      +    ListNode? front, rear; // 先頭ノード front、末尾ノード rear
      +    int queSize = 0;      // 両端キューの長さ
      +
      +    public LinkedListDeque() {
      +        front = null;
      +        rear = null;
      +    }
      +
      +    /* 両端キューの長さを取得 */
      +    public int Size() {
      +        return queSize;
      +    }
      +
      +    /* 両端キューが空かどうかを判定 */
      +    public bool IsEmpty() {
      +        return Size() == 0;
      +    }
      +
      +    /* エンキュー操作 */
      +    void Push(int num, bool isFront) {
      +        ListNode node = new(num);
      +        // 連結リストが空なら、front と rear の両方を node に向ける
      +        if (IsEmpty()) {
      +            front = node;
      +            rear = node;
      +        }
      +        // 先頭へのエンキュー操作
      +        else if (isFront) {
      +            // node を連結リストの先頭に追加
      +            front!.prev = node;
      +            node.next = front;
      +            front = node; // 先頭ノードを更新する
      +        }
      +        // 末尾へのエンキュー操作
      +        else {
      +            // node を連結リストの末尾に追加
      +            rear!.next = node;
      +            node.prev = rear;
      +            rear = node;  // 末尾ノードを更新する
      +        }
      +
      +        queSize++; // キューの長さを更新
      +    }
      +
      +    /* キュー先頭にエンキュー */
      +    public void PushFirst(int num) {
      +        Push(num, true);
      +    }
      +
      +    /* キュー末尾にエンキュー */
      +    public void PushLast(int num) {
      +        Push(num, false);
      +    }
      +
      +    /* デキュー操作 */
      +    int? Pop(bool isFront) {
      +        if (IsEmpty())
      +            throw new Exception();
      +        int? val;
      +        // キュー先頭からの取り出し
      +        if (isFront) {
      +            val = front?.val; // 先頭ノードの値を一時保存
      +            // 先頭ノードを削除
      +            ListNode? fNext = front?.next;
      +            if (fNext != null) {
      +                fNext.prev = null;
      +                front!.next = null;
      +            }
      +            front = fNext;   // 先頭ノードを更新する
      +        }
      +        // キュー末尾からの取り出し
      +        else {
      +            val = rear?.val;  // 末尾ノードの値を一時保存
      +            // 末尾ノードを削除
      +            ListNode? rPrev = rear?.prev;
      +            if (rPrev != null) {
      +                rPrev.next = null;
      +                rear!.prev = null;
      +            }
      +            rear = rPrev;    // 末尾ノードを更新する
      +        }
      +
      +        queSize--; // キューの長さを更新
      +        return val;
      +    }
      +
      +    /* キュー先頭からデキュー */
      +    public int? PopFirst() {
      +        return Pop(true);
      +    }
      +
      +    /* キュー末尾からデキュー */
      +    public int? PopLast() {
      +        return Pop(false);
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    public int? PeekFirst() {
      +        if (IsEmpty())
      +            throw new Exception();
      +        return front?.val;
      +    }
      +
      +    /* キュー末尾の要素にアクセス */
      +    public int? PeekLast() {
      +        if (IsEmpty())
      +            throw new Exception();
      +        return rear?.val;
      +    }
      +
      +    /* 出力用の配列を返す */
      +    public int?[] ToArray() {
      +        ListNode? node = front;
      +        int?[] res = new int?[Size()];
      +        for (int i = 0; i < res.Length; i++) {
      +            res[i] = node?.val;
      +            node = node?.next;
      +        }
      +
      +        return res;
      +    }
      +}
       
      -
      linkedlist_deque.swift
      [class]{ListNode}-[func]{}
      -
      -[class]{LinkedListDeque}-[func]{}
      +
      linkedlist_deque.go
      /* 双方向連結リストベースの両端キュー */
      +type linkedListDeque struct {
      +    // 組み込みパッケージ list を使う
      +    data *list.List
      +}
      +
      +/* 両端キューを初期化する */
      +func newLinkedListDeque() *linkedListDeque {
      +    return &linkedListDeque{
      +        data: list.New(),
      +    }
      +}
      +
      +/* キュー先頭に要素を追加する */
      +func (s *linkedListDeque) pushFirst(value any) {
      +    s.data.PushFront(value)
      +}
      +
      +/* キュー末尾に要素を追加する */
      +func (s *linkedListDeque) pushLast(value any) {
      +    s.data.PushBack(value)
      +}
      +
      +/* 先頭要素を取り出す */
      +func (s *linkedListDeque) popFirst() any {
      +    if s.isEmpty() {
      +        return nil
      +    }
      +    e := s.data.Front()
      +    s.data.Remove(e)
      +    return e.Value
      +}
      +
      +/* 末尾要素を取り出す */
      +func (s *linkedListDeque) popLast() any {
      +    if s.isEmpty() {
      +        return nil
      +    }
      +    e := s.data.Back()
      +    s.data.Remove(e)
      +    return e.Value
      +}
      +
      +/* キュー先頭の要素にアクセス */
      +func (s *linkedListDeque) peekFirst() any {
      +    if s.isEmpty() {
      +        return nil
      +    }
      +    e := s.data.Front()
      +    return e.Value
      +}
      +
      +/* キュー末尾の要素にアクセス */
      +func (s *linkedListDeque) peekLast() any {
      +    if s.isEmpty() {
      +        return nil
      +    }
      +    e := s.data.Back()
      +    return e.Value
      +}
      +
      +/* キューの長さを取得 */
      +func (s *linkedListDeque) size() int {
      +    return s.data.Len()
      +}
      +
      +/* キューが空かどうかを判定 */
      +func (s *linkedListDeque) isEmpty() bool {
      +    return s.data.Len() == 0
      +}
      +
      +/* 表示用に List を取得 */
      +func (s *linkedListDeque) toList() *list.List {
      +    return s.data
      +}
       
      -
      linkedlist_deque.js
      [class]{ListNode}-[func]{}
      -
      -[class]{LinkedListDeque}-[func]{}
      +
      linkedlist_deque.swift
      /* 双方向連結リストノード */
      +class ListNode {
      +    var val: Int // ノード値
      +    var next: ListNode? // 後続ノードへの参照
      +    weak var prev: ListNode? // 前駆ノードへの参照
      +
      +    init(val: Int) {
      +        self.val = val
      +    }
      +}
      +
      +/* 双方向連結リストベースの両端キュー */
      +class LinkedListDeque {
      +    private var front: ListNode? // 先頭ノード front
      +    private var rear: ListNode? // 末尾ノード rear
      +    private var _size: Int // 両端キューの長さ
      +
      +    init() {
      +        _size = 0
      +    }
      +
      +    /* 両端キューの長さを取得 */
      +    func size() -> Int {
      +        _size
      +    }
      +
      +    /* 両端キューが空かどうかを判定 */
      +    func isEmpty() -> Bool {
      +        size() == 0
      +    }
      +
      +    /* エンキュー操作 */
      +    private func push(num: Int, isFront: Bool) {
      +        let node = ListNode(val: num)
      +        // 連結リストが空なら、front と rear の両方を node に向ける
      +        if isEmpty() {
      +            front = node
      +            rear = node
      +        }
      +        // 先頭へのエンキュー操作
      +        else if isFront {
      +            // node を連結リストの先頭に追加
      +            front?.prev = node
      +            node.next = front
      +            front = node // 先頭ノードを更新する
      +        }
      +        // 末尾へのエンキュー操作
      +        else {
      +            // node を連結リストの末尾に追加
      +            rear?.next = node
      +            node.prev = rear
      +            rear = node // 末尾ノードを更新する
      +        }
      +        _size += 1 // キューの長さを更新
      +    }
      +
      +    /* キュー先頭にエンキュー */
      +    func pushFirst(num: Int) {
      +        push(num: num, isFront: true)
      +    }
      +
      +    /* キュー末尾にエンキュー */
      +    func pushLast(num: Int) {
      +        push(num: num, isFront: false)
      +    }
      +
      +    /* デキュー操作 */
      +    private func pop(isFront: Bool) -> Int {
      +        if isEmpty() {
      +            fatalError("両端キューが空です")
      +        }
      +        let val: Int
      +        // キュー先頭からの取り出し
      +        if isFront {
      +            val = front!.val // 先頭ノードの値を一時保存
      +            // 先頭ノードを削除
      +            let fNext = front?.next
      +            if fNext != nil {
      +                fNext?.prev = nil
      +                front?.next = nil
      +            }
      +            front = fNext // 先頭ノードを更新する
      +        }
      +        // キュー末尾からの取り出し
      +        else {
      +            val = rear!.val // 末尾ノードの値を一時保存
      +            // 末尾ノードを削除
      +            let rPrev = rear?.prev
      +            if rPrev != nil {
      +                rPrev?.next = nil
      +                rear?.prev = nil
      +            }
      +            rear = rPrev // 末尾ノードを更新する
      +        }
      +        _size -= 1 // キューの長さを更新
      +        return val
      +    }
      +
      +    /* キュー先頭からデキュー */
      +    func popFirst() -> Int {
      +        pop(isFront: true)
      +    }
      +
      +    /* キュー末尾からデキュー */
      +    func popLast() -> Int {
      +        pop(isFront: false)
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    func peekFirst() -> Int {
      +        if isEmpty() {
      +            fatalError("両端キューが空です")
      +        }
      +        return front!.val
      +    }
      +
      +    /* キュー末尾の要素にアクセス */
      +    func peekLast() -> Int {
      +        if isEmpty() {
      +            fatalError("両端キューが空です")
      +        }
      +        return rear!.val
      +    }
      +
      +    /* 出力用の配列を返す */
      +    func toArray() -> [Int] {
      +        var node = front
      +        var res = Array(repeating: 0, count: size())
      +        for i in res.indices {
      +            res[i] = node!.val
      +            node = node?.next
      +        }
      +        return res
      +    }
      +}
       
      -
      linkedlist_deque.ts
      [class]{ListNode}-[func]{}
      -
      -[class]{LinkedListDeque}-[func]{}
      +
      linkedlist_deque.js
      /* 双方向連結リストノード */
      +class ListNode {
      +    prev; // 前駆ノードへの参照(ポインタ)
      +    next; // 後継ノードへの参照(ポインタ)
      +    val; // ノード値
      +
      +    constructor(val) {
      +        this.val = val;
      +        this.next = null;
      +        this.prev = null;
      +    }
      +}
      +
      +/* 双方向連結リストベースの両端キュー */
      +class LinkedListDeque {
      +    #front; // 先頭ノード front
      +    #rear; // 末尾ノード rear
      +    #queSize; // 両端キューの長さ
      +
      +    constructor() {
      +        this.#front = null;
      +        this.#rear = null;
      +        this.#queSize = 0;
      +    }
      +
      +    /* 末尾へのエンキュー操作 */
      +    pushLast(val) {
      +        const node = new ListNode(val);
      +        // 連結リストが空なら、front と rear の両方を node に向ける
      +        if (this.#queSize === 0) {
      +            this.#front = node;
      +            this.#rear = node;
      +        } else {
      +            // node を連結リストの末尾に追加
      +            this.#rear.next = node;
      +            node.prev = this.#rear;
      +            this.#rear = node; // 末尾ノードを更新する
      +        }
      +        this.#queSize++;
      +    }
      +
      +    /* 先頭へのエンキュー操作 */
      +    pushFirst(val) {
      +        const node = new ListNode(val);
      +        // 連結リストが空なら、front と rear の両方を node に向ける
      +        if (this.#queSize === 0) {
      +            this.#front = node;
      +            this.#rear = node;
      +        } else {
      +            // node を連結リストの先頭に追加
      +            this.#front.prev = node;
      +            node.next = this.#front;
      +            this.#front = node; // 先頭ノードを更新する
      +        }
      +        this.#queSize++;
      +    }
      +
      +    /* キュー末尾からの取り出し */
      +    popLast() {
      +        if (this.#queSize === 0) {
      +            return null;
      +        }
      +        const value = this.#rear.val; // 末尾ノードの値を保存する
      +        // 末尾ノードを削除
      +        let temp = this.#rear.prev;
      +        if (temp !== null) {
      +            temp.next = null;
      +            this.#rear.prev = null;
      +        }
      +        this.#rear = temp; // 末尾ノードを更新する
      +        this.#queSize--;
      +        return value;
      +    }
      +
      +    /* キュー先頭からの取り出し */
      +    popFirst() {
      +        if (this.#queSize === 0) {
      +            return null;
      +        }
      +        const value = this.#front.val; // 末尾ノードの値を保存する
      +        // 先頭ノードを削除
      +        let temp = this.#front.next;
      +        if (temp !== null) {
      +            temp.prev = null;
      +            this.#front.next = null;
      +        }
      +        this.#front = temp; // 先頭ノードを更新する
      +        this.#queSize--;
      +        return value;
      +    }
      +
      +    /* キュー末尾の要素にアクセス */
      +    peekLast() {
      +        return this.#queSize === 0 ? null : this.#rear.val;
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    peekFirst() {
      +        return this.#queSize === 0 ? null : this.#front.val;
      +    }
      +
      +    /* 両端キューの長さを取得 */
      +    size() {
      +        return this.#queSize;
      +    }
      +
      +    /* 両端キューが空かどうかを判定 */
      +    isEmpty() {
      +        return this.#queSize === 0;
      +    }
      +
      +    /* 両端キューを出力する */
      +    print() {
      +        const arr = [];
      +        let temp = this.#front;
      +        while (temp !== null) {
      +            arr.push(temp.val);
      +            temp = temp.next;
      +        }
      +        console.log('[' + arr.join(', ') + ']');
      +    }
      +}
       
      -
      linkedlist_deque.dart
      [class]{ListNode}-[func]{}
      -
      -[class]{LinkedListDeque}-[func]{}
      +
      linkedlist_deque.ts
      /* 双方向連結リストノード */
      +class ListNode {
      +    prev: ListNode; // 前駆ノードへの参照(ポインタ)
      +    next: ListNode; // 後継ノードへの参照(ポインタ)
      +    val: number; // ノード値
      +
      +    constructor(val: number) {
      +        this.val = val;
      +        this.next = null;
      +        this.prev = null;
      +    }
      +}
      +
      +/* 双方向連結リストベースの両端キュー */
      +class LinkedListDeque {
      +    private front: ListNode; // 先頭ノード front
      +    private rear: ListNode; // 末尾ノード rear
      +    private queSize: number; // 両端キューの長さ
      +
      +    constructor() {
      +        this.front = null;
      +        this.rear = null;
      +        this.queSize = 0;
      +    }
      +
      +    /* 末尾へのエンキュー操作 */
      +    pushLast(val: number): void {
      +        const node: ListNode = new ListNode(val);
      +        // 連結リストが空なら、front と rear の両方を node に向ける
      +        if (this.queSize === 0) {
      +            this.front = node;
      +            this.rear = node;
      +        } else {
      +            // node を連結リストの末尾に追加
      +            this.rear.next = node;
      +            node.prev = this.rear;
      +            this.rear = node; // 末尾ノードを更新する
      +        }
      +        this.queSize++;
      +    }
      +
      +    /* 先頭へのエンキュー操作 */
      +    pushFirst(val: number): void {
      +        const node: ListNode = new ListNode(val);
      +        // 連結リストが空なら、front と rear の両方を node に向ける
      +        if (this.queSize === 0) {
      +            this.front = node;
      +            this.rear = node;
      +        } else {
      +            // node を連結リストの先頭に追加
      +            this.front.prev = node;
      +            node.next = this.front;
      +            this.front = node; // 先頭ノードを更新する
      +        }
      +        this.queSize++;
      +    }
      +
      +    /* キュー末尾からの取り出し */
      +    popLast(): number {
      +        if (this.queSize === 0) {
      +            return null;
      +        }
      +        const value: number = this.rear.val; // 末尾ノードの値を保存する
      +        // 末尾ノードを削除
      +        let temp: ListNode = this.rear.prev;
      +        if (temp !== null) {
      +            temp.next = null;
      +            this.rear.prev = null;
      +        }
      +        this.rear = temp; // 末尾ノードを更新する
      +        this.queSize--;
      +        return value;
      +    }
      +
      +    /* キュー先頭からの取り出し */
      +    popFirst(): number {
      +        if (this.queSize === 0) {
      +            return null;
      +        }
      +        const value: number = this.front.val; // 末尾ノードの値を保存する
      +        // 先頭ノードを削除
      +        let temp: ListNode = this.front.next;
      +        if (temp !== null) {
      +            temp.prev = null;
      +            this.front.next = null;
      +        }
      +        this.front = temp; // 先頭ノードを更新する
      +        this.queSize--;
      +        return value;
      +    }
      +
      +    /* キュー末尾の要素にアクセス */
      +    peekLast(): number {
      +        return this.queSize === 0 ? null : this.rear.val;
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    peekFirst(): number {
      +        return this.queSize === 0 ? null : this.front.val;
      +    }
      +
      +    /* 両端キューの長さを取得 */
      +    size(): number {
      +        return this.queSize;
      +    }
      +
      +    /* 両端キューが空かどうかを判定 */
      +    isEmpty(): boolean {
      +        return this.queSize === 0;
      +    }
      +
      +    /* 両端キューを出力する */
      +    print(): void {
      +        const arr: number[] = [];
      +        let temp: ListNode = this.front;
      +        while (temp !== null) {
      +            arr.push(temp.val);
      +            temp = temp.next;
      +        }
      +        console.log('[' + arr.join(', ') + ']');
      +    }
      +}
       
      -
      linkedlist_deque.rs
      [class]{ListNode}-[func]{}
      -
      -[class]{LinkedListDeque}-[func]{}
      +
      linkedlist_deque.dart
      /* 双方向連結リストノード */
      +class ListNode {
      +  int val; // ノード値
      +  ListNode? next; // 後続ノードへの参照
      +  ListNode? prev; // 前駆ノードへの参照
      +
      +  ListNode(this.val, {this.next, this.prev});
      +}
      +
      +/* 双方向連結リストに基づく双方向キュー */
      +class LinkedListDeque {
      +  late ListNode? _front; // 先頭ノード _front
      +  late ListNode? _rear; // 末尾ノード _rear
      +  int _queSize = 0; // 両端キューの長さ
      +
      +  LinkedListDeque() {
      +    this._front = null;
      +    this._rear = null;
      +  }
      +
      +  /* 両端キューの長さを取得 */
      +  int size() {
      +    return this._queSize;
      +  }
      +
      +  /* 両端キューが空かどうかを判定 */
      +  bool isEmpty() {
      +    return size() == 0;
      +  }
      +
      +  /* エンキュー操作 */
      +  void push(int _num, bool isFront) {
      +    final ListNode node = ListNode(_num);
      +    if (isEmpty()) {
      +      // 連結リストが空なら、`_front` と `_rear` の両方を `node` に向ける
      +      _front = _rear = node;
      +    } else if (isFront) {
      +      // 先頭へのエンキュー操作
      +      // node を連結リストの先頭に追加する
      +      _front!.prev = node;
      +      node.next = _front;
      +      _front = node; // 先頭ノードを更新する
      +    } else {
      +      // 末尾へのエンキュー操作
      +      // node を連結リストの末尾に追加する
      +      _rear!.next = node;
      +      node.prev = _rear;
      +      _rear = node; // 末尾ノードを更新する
      +    }
      +    _queSize++; // キューの長さを更新
      +  }
      +
      +  /* キュー先頭にエンキュー */
      +  void pushFirst(int _num) {
      +    push(_num, true);
      +  }
      +
      +  /* キュー末尾にエンキュー */
      +  void pushLast(int _num) {
      +    push(_num, false);
      +  }
      +
      +  /* デキュー操作 */
      +  int? pop(bool isFront) {
      +    // キューが空なら、そのまま `null` を返す
      +    if (isEmpty()) {
      +      return null;
      +    }
      +    final int val;
      +    if (isFront) {
      +      // キュー先頭からの取り出し
      +      val = _front!.val; // 先頭ノードの値を一時保存
      +      // 先頭ノードを削除
      +      ListNode? fNext = _front!.next;
      +      if (fNext != null) {
      +        fNext.prev = null;
      +        _front!.next = null;
      +      }
      +      _front = fNext; // 先頭ノードを更新する
      +    } else {
      +      // キュー末尾からの取り出し
      +      val = _rear!.val; // 末尾ノードの値を一時保存
      +      // 末尾ノードを削除
      +      ListNode? rPrev = _rear!.prev;
      +      if (rPrev != null) {
      +        rPrev.next = null;
      +        _rear!.prev = null;
      +      }
      +      _rear = rPrev; // 末尾ノードを更新する
      +    }
      +    _queSize--; // キューの長さを更新
      +    return val;
      +  }
      +
      +  /* キュー先頭からデキュー */
      +  int? popFirst() {
      +    return pop(true);
      +  }
      +
      +  /* キュー末尾からデキュー */
      +  int? popLast() {
      +    return pop(false);
      +  }
      +
      +  /* キュー先頭の要素にアクセス */
      +  int? peekFirst() {
      +    return _front?.val;
      +  }
      +
      +  /* キュー末尾の要素にアクセス */
      +  int? peekLast() {
      +    return _rear?.val;
      +  }
      +
      +  /* 出力用の配列を返す */
      +  List<int> toArray() {
      +    ListNode? node = _front;
      +    final List<int> res = [];
      +    for (int i = 0; i < _queSize; i++) {
      +      res.add(node!.val);
      +      node = node.next;
      +    }
      +    return res;
      +  }
      +}
       
      -
      linkedlist_deque.c
      [class]{DoublyListNode}-[func]{}
      -
      -[class]{LinkedListDeque}-[func]{}
      +
      linkedlist_deque.rs
      /* 双方向連結リストノード */
      +pub struct ListNode<T> {
      +    pub val: T,                                 // ノード値
      +    pub next: Option<Rc<RefCell<ListNode<T>>>>, // 後継ノードへのポインタ
      +    pub prev: Option<Rc<RefCell<ListNode<T>>>>, // 前駆ノードへのポインタ
      +}
      +
      +impl<T> ListNode<T> {
      +    pub fn new(val: T) -> Rc<RefCell<ListNode<T>>> {
      +        Rc::new(RefCell::new(ListNode {
      +            val,
      +            next: None,
      +            prev: None,
      +        }))
      +    }
      +}
      +
      +/* 双方向連結リストベースの両端キュー */
      +#[allow(dead_code)]
      +pub struct LinkedListDeque<T> {
      +    front: Option<Rc<RefCell<ListNode<T>>>>, // 先頭ノード front
      +    rear: Option<Rc<RefCell<ListNode<T>>>>,  // 末尾ノード rear
      +    que_size: usize,                         // 両端キューの長さ
      +}
      +
      +impl<T: Copy> LinkedListDeque<T> {
      +    pub fn new() -> Self {
      +        Self {
      +            front: None,
      +            rear: None,
      +            que_size: 0,
      +        }
      +    }
      +
      +    /* 両端キューの長さを取得 */
      +    pub fn size(&self) -> usize {
      +        return self.que_size;
      +    }
      +
      +    /* 両端キューが空かどうかを判定 */
      +    pub fn is_empty(&self) -> bool {
      +        return self.que_size == 0;
      +    }
      +
      +    /* エンキュー操作 */
      +    fn push(&mut self, num: T, is_front: bool) {
      +        let node = ListNode::new(num);
      +        // 先頭へのエンキュー操作
      +        if is_front {
      +            match self.front.take() {
      +                // 連結リストが空なら、front と rear の両方を node に向ける
      +                None => {
      +                    self.rear = Some(node.clone());
      +                    self.front = Some(node);
      +                }
      +                // node を連結リストの先頭に追加
      +                Some(old_front) => {
      +                    old_front.borrow_mut().prev = Some(node.clone());
      +                    node.borrow_mut().next = Some(old_front);
      +                    self.front = Some(node); // 先頭ノードを更新する
      +                }
      +            }
      +        }
      +        // 末尾へのエンキュー操作
      +        else {
      +            match self.rear.take() {
      +                // 連結リストが空なら、front と rear の両方を node に向ける
      +                None => {
      +                    self.front = Some(node.clone());
      +                    self.rear = Some(node);
      +                }
      +                // node を連結リストの末尾に追加
      +                Some(old_rear) => {
      +                    old_rear.borrow_mut().next = Some(node.clone());
      +                    node.borrow_mut().prev = Some(old_rear);
      +                    self.rear = Some(node); // 末尾ノードを更新する
      +                }
      +            }
      +        }
      +        self.que_size += 1; // キューの長さを更新
      +    }
      +
      +    /* キュー先頭にエンキュー */
      +    pub fn push_first(&mut self, num: T) {
      +        self.push(num, true);
      +    }
      +
      +    /* キュー末尾にエンキュー */
      +    pub fn push_last(&mut self, num: T) {
      +        self.push(num, false);
      +    }
      +
      +    /* デキュー操作 */
      +    fn pop(&mut self, is_front: bool) -> Option<T> {
      +        // キューが空なら、そのまま `None` を返す
      +        if self.is_empty() {
      +            return None;
      +        };
      +        // キュー先頭からの取り出し
      +        if is_front {
      +            self.front.take().map(|old_front| {
      +                match old_front.borrow_mut().next.take() {
      +                    Some(new_front) => {
      +                        new_front.borrow_mut().prev.take();
      +                        self.front = Some(new_front); // 先頭ノードを更新する
      +                    }
      +                    None => {
      +                        self.rear.take();
      +                    }
      +                }
      +                self.que_size -= 1; // キューの長さを更新
      +                old_front.borrow().val
      +            })
      +        }
      +        // キュー末尾からの取り出し
      +        else {
      +            self.rear.take().map(|old_rear| {
      +                match old_rear.borrow_mut().prev.take() {
      +                    Some(new_rear) => {
      +                        new_rear.borrow_mut().next.take();
      +                        self.rear = Some(new_rear); // 末尾ノードを更新する
      +                    }
      +                    None => {
      +                        self.front.take();
      +                    }
      +                }
      +                self.que_size -= 1; // キューの長さを更新
      +                old_rear.borrow().val
      +            })
      +        }
      +    }
      +
      +    /* キュー先頭からデキュー */
      +    pub fn pop_first(&mut self) -> Option<T> {
      +        return self.pop(true);
      +    }
      +
      +    /* キュー末尾からデキュー */
      +    pub fn pop_last(&mut self) -> Option<T> {
      +        return self.pop(false);
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    pub fn peek_first(&self) -> Option<&Rc<RefCell<ListNode<T>>>> {
      +        self.front.as_ref()
      +    }
      +
      +    /* キュー末尾の要素にアクセス */
      +    pub fn peek_last(&self) -> Option<&Rc<RefCell<ListNode<T>>>> {
      +        self.rear.as_ref()
      +    }
      +
      +    /* 出力用の配列を返す */
      +    pub fn to_array(&self, head: Option<&Rc<RefCell<ListNode<T>>>>) -> Vec<T> {
      +        let mut res: Vec<T> = Vec::new();
      +        fn recur<T: Copy>(cur: Option<&Rc<RefCell<ListNode<T>>>>, res: &mut Vec<T>) {
      +            if let Some(cur) = cur {
      +                res.push(cur.borrow().val);
      +                recur(cur.borrow().next.as_ref(), res);
      +            }
      +        }
      +
      +        recur(head, &mut res);
      +        res
      +    }
      +}
       
      -
      linkedlist_deque.kt
      [class]{ListNode}-[func]{}
      -
      -[class]{LinkedListDeque}-[func]{}
      +
      linkedlist_deque.c
      /* 双方向連結リストノード */
      +typedef struct DoublyListNode {
      +    int val;                     // ノード値
      +    struct DoublyListNode *next; // 後続ノード
      +    struct DoublyListNode *prev; // 前駆ノード
      +} DoublyListNode;
      +
      +/* コンストラクタ */
      +DoublyListNode *newDoublyListNode(int num) {
      +    DoublyListNode *new = (DoublyListNode *)malloc(sizeof(DoublyListNode));
      +    new->val = num;
      +    new->next = NULL;
      +    new->prev = NULL;
      +    return new;
      +}
      +
      +/* デストラクタ */
      +void delDoublyListNode(DoublyListNode *node) {
      +    free(node);
      +}
      +
      +/* 双方向連結リストベースの両端キュー */
      +typedef struct {
      +    DoublyListNode *front, *rear; // 先頭ノード front、末尾ノード rear
      +    int queSize;                  // 両端キューの長さ
      +} LinkedListDeque;
      +
      +/* コンストラクタ */
      +LinkedListDeque *newLinkedListDeque() {
      +    LinkedListDeque *deque = (LinkedListDeque *)malloc(sizeof(LinkedListDeque));
      +    deque->front = NULL;
      +    deque->rear = NULL;
      +    deque->queSize = 0;
      +    return deque;
      +}
      +
      +/* デストラクタ */
      +void delLinkedListdeque(LinkedListDeque *deque) {
      +    // すべてのノードを解放
      +    for (int i = 0; i < deque->queSize && deque->front != NULL; i++) {
      +        DoublyListNode *tmp = deque->front;
      +        deque->front = deque->front->next;
      +        free(tmp);
      +    }
      +    // deque 構造体を解放する
      +    free(deque);
      +}
      +
      +/* キューの長さを取得 */
      +int size(LinkedListDeque *deque) {
      +    return deque->queSize;
      +}
      +
      +/* キューが空かどうかを判定 */
      +bool empty(LinkedListDeque *deque) {
      +    return (size(deque) == 0);
      +}
      +
      +/* エンキュー */
      +void push(LinkedListDeque *deque, int num, bool isFront) {
      +    DoublyListNode *node = newDoublyListNode(num);
      +    // 連結リストが空なら、`front` と `rear` の両方を `node` に向ける
      +    if (empty(deque)) {
      +        deque->front = deque->rear = node;
      +    }
      +    // 先頭へのエンキュー操作
      +    else if (isFront) {
      +        // node を連結リストの先頭に追加
      +        deque->front->prev = node;
      +        node->next = deque->front;
      +        deque->front = node; // 先頭ノードを更新する
      +    }
      +    // 末尾へのエンキュー操作
      +    else {
      +        // node を連結リストの末尾に追加
      +        deque->rear->next = node;
      +        node->prev = deque->rear;
      +        deque->rear = node;
      +    }
      +    deque->queSize++; // キューの長さを更新
      +}
      +
      +/* キュー先頭にエンキュー */
      +void pushFirst(LinkedListDeque *deque, int num) {
      +    push(deque, num, true);
      +}
      +
      +/* キュー末尾にエンキュー */
      +void pushLast(LinkedListDeque *deque, int num) {
      +    push(deque, num, false);
      +}
      +
      +/* キュー先頭の要素にアクセス */
      +int peekFirst(LinkedListDeque *deque) {
      +    assert(size(deque) && deque->front);
      +    return deque->front->val;
      +}
      +
      +/* キュー末尾の要素にアクセス */
      +int peekLast(LinkedListDeque *deque) {
      +    assert(size(deque) && deque->rear);
      +    return deque->rear->val;
      +}
      +
      +/* デキュー */
      +int pop(LinkedListDeque *deque, bool isFront) {
      +    if (empty(deque))
      +        return -1;
      +    int val;
      +    // キュー先頭からの取り出し
      +    if (isFront) {
      +        val = peekFirst(deque); // 先頭ノードの値を一時保存
      +        DoublyListNode *fNext = deque->front->next;
      +        if (fNext) {
      +            fNext->prev = NULL;
      +            deque->front->next = NULL;
      +        }
      +        delDoublyListNode(deque->front);
      +        deque->front = fNext; // 先頭ノードを更新する
      +    }
      +    // キュー末尾からの取り出し
      +    else {
      +        val = peekLast(deque); // 末尾ノードの値を一時保存
      +        DoublyListNode *rPrev = deque->rear->prev;
      +        if (rPrev) {
      +            rPrev->next = NULL;
      +            deque->rear->prev = NULL;
      +        }
      +        delDoublyListNode(deque->rear);
      +        deque->rear = rPrev; // 末尾ノードを更新する
      +    }
      +    deque->queSize--; // キューの長さを更新
      +    return val;
      +}
      +
      +/* キュー先頭からデキュー */
      +int popFirst(LinkedListDeque *deque) {
      +    return pop(deque, true);
      +}
      +
      +/* キュー末尾からデキュー */
      +int popLast(LinkedListDeque *deque) {
      +    return pop(deque, false);
      +}
      +
      +/* キューを出力する */
      +void printLinkedListDeque(LinkedListDeque *deque) {
      +    int *arr = malloc(sizeof(int) * deque->queSize);
      +    // 連結リスト内のデータを配列にコピー
      +    int i;
      +    DoublyListNode *node;
      +    for (i = 0, node = deque->front; i < deque->queSize; i++) {
      +        arr[i] = node->val;
      +        node = node->next;
      +    }
      +    printArray(arr, deque->queSize);
      +    free(arr);
      +}
       
      -
      linkedlist_deque.rb
      [class]{ListNode}-[func]{}
      -
      -[class]{LinkedListDeque}-[func]{}
      +
      linkedlist_deque.kt
      /* 双方向連結リストノード */
      +class ListNode(var _val: Int) {
      +    // ノード値
      +    var next: ListNode? = null // 後続ノードへの参照
      +    var prev: ListNode? = null // 前駆ノードへの参照
      +}
      +
      +/* 双方向連結リストベースの両端キュー */
      +class LinkedListDeque {
      +    private var front: ListNode? = null // 先頭ノード front
      +    private var rear: ListNode? = null // 末尾ノード rear
      +    private var queSize: Int = 0 // 両端キューの長さ
      +
      +    /* 両端キューの長さを取得 */
      +    fun size(): Int {
      +        return queSize
      +    }
      +
      +    /* 両端キューが空かどうかを判定 */
      +    fun isEmpty(): Boolean {
      +        return size() == 0
      +    }
      +
      +    /* エンキュー操作 */
      +    fun push(num: Int, isFront: Boolean) {
      +        val node = ListNode(num)
      +        // 連結リストが空なら、front と rear の両方を node に向ける
      +        if (isEmpty()) {
      +            rear = node
      +            front = rear
      +            // 先頭へのエンキュー操作
      +        } else if (isFront) {
      +            // node を連結リストの先頭に追加
      +            front?.prev = node
      +            node.next = front
      +            front = node // 先頭ノードを更新する
      +            // 末尾へのエンキュー操作
      +        } else {
      +            // node を連結リストの末尾に追加
      +            rear?.next = node
      +            node.prev = rear
      +            rear = node // 末尾ノードを更新する
      +        }
      +        queSize++ // キューの長さを更新
      +    }
      +
      +    /* キュー先頭にエンキュー */
      +    fun pushFirst(num: Int) {
      +        push(num, true)
      +    }
      +
      +    /* キュー末尾にエンキュー */
      +    fun pushLast(num: Int) {
      +        push(num, false)
      +    }
      +
      +    /* デキュー操作 */
      +    fun pop(isFront: Boolean): Int {
      +        if (isEmpty()) 
      +            throw IndexOutOfBoundsException()
      +        val _val: Int
      +        // キュー先頭からの取り出し
      +        if (isFront) {
      +            _val = front!!._val // 先頭ノードの値を一時保存
      +            // 先頭ノードを削除
      +            val fNext = front!!.next
      +            if (fNext != null) {
      +                fNext.prev = null
      +                front!!.next = null
      +            }
      +            front = fNext // 先頭ノードを更新する
      +            // キュー末尾からの取り出し
      +        } else {
      +            _val = rear!!._val // 末尾ノードの値を一時保存
      +            // 末尾ノードを削除
      +            val rPrev = rear!!.prev
      +            if (rPrev != null) {
      +                rPrev.next = null
      +                rear!!.prev = null
      +            }
      +            rear = rPrev // 末尾ノードを更新する
      +        }
      +        queSize-- // キューの長さを更新
      +        return _val
      +    }
      +
      +    /* キュー先頭からデキュー */
      +    fun popFirst(): Int {
      +        return pop(true)
      +    }
      +
      +    /* キュー末尾からデキュー */
      +    fun popLast(): Int {
      +        return pop(false)
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    fun peekFirst(): Int {
      +        if (isEmpty()) throw IndexOutOfBoundsException()
      +        return front!!._val
      +    }
      +
      +    /* キュー末尾の要素にアクセス */
      +    fun peekLast(): Int {
      +        if (isEmpty()) throw IndexOutOfBoundsException()
      +        return rear!!._val
      +    }
      +
      +    /* 出力用の配列を返す */
      +    fun toArray(): IntArray {
      +        var node = front
      +        val res = IntArray(size())
      +        for (i in res.indices) {
      +            res[i] = node!!._val
      +            node = node.next
      +        }
      +        return res
      +    }
      +}
      +
      +
      +
      +
      linkedlist_deque.rb
      =begin
      +File: linkedlist_deque.rb
      +Created Time: 2024-04-06
      +Author: Xuan Khoa Tu Nguyen (ngxktuzkai2000@gmail.com)
      +=end
      +
      +# ## 双方向連結リストノード
      +class ListNode
      +  attr_accessor :val
      +  attr_accessor :next # 後続ノードへの参照
      +  attr_accessor :prev # 前ノードへの参照
      +
      +  ### コンストラクタ ###
      +  def initialize(val)
      +    @val = val
      +  end
      +end
      +
      +### 双方向連結リストで実装した両端キュー ###
      +class LinkedListDeque
      +  ### 両端キューの長さを取得 ###
      +  attr_reader :size
      +
      +  ### コンストラクタ ###
      +  def initialize
      +    @front = nil  # 先頭ノード front
      +    @rear = nil   # 末尾ノード rear
      +    @size = 0     # 両端キューの長さ
      +  end
      +
      +  ### 両端キューが空か判定 ###
      +  def is_empty?
      +    size.zero?
      +  end
      +
      +  ### エンキュー操作 ###
      +  def push(num, is_front)
      +    node = ListNode.new(num)
      +    # 連結リストが空なら、`front` と `rear` の両方を `node` に向ける
      +    if is_empty?
      +      @front = @rear = node
      +    # 先頭へのエンキュー操作
      +    elsif is_front
      +      # node を連結リストの先頭に追加
      +      @front.prev = node
      +      node.next = @front
      +      @front = node # 先頭ノードを更新する
      +    # 末尾へのエンキュー操作
      +    else
      +      # node を連結リストの末尾に追加
      +      @rear.next = node
      +      node.prev = @rear
      +      @rear = node # 末尾ノードを更新する
      +    end
      +    @size += 1 # キューの長さを更新
      +  end
      +
      +  ### キュー先頭に追加 ###
      +  def push_first(num)
      +    push(num, true)
      +  end
      +
      +  ### キュー末尾に追加 ###
      +  def push_last(num)
      +    push(num, false)
      +  end
      +
      +  ### デキュー操作 ###
      +  def pop(is_front)
      +    raise IndexError, '両端キューは空です' if is_empty?
      +
      +    # キュー先頭からの取り出し
      +    if is_front
      +      val = @front.val # 先頭ノードの値を一時保存
      +      # 先頭ノードを削除
      +      fnext = @front.next
      +      unless fnext.nil?
      +        fnext.prev = nil
      +        @front.next = nil
      +      end
      +      @front = fnext # 先頭ノードを更新する
      +    # キュー末尾からの取り出し
      +    else
      +      val = @rear.val # 末尾ノードの値を一時保存
      +      # 末尾ノードを削除
      +      rprev = @rear.prev
      +      unless rprev.nil?
      +        rprev.next = nil
      +        @rear.prev = nil
      +      end
      +      @rear = rprev # 末尾ノードを更新する
      +    end
      +    @size -= 1 # キューの長さを更新
      +
      +    val
      +  end
      +
      +  ### キュー先頭から取り出す ###
      +  def pop_first
      +    pop(true)
      +  end
      +
      +  ### キュー先頭から取り出す ###
      +  def pop_last
      +    pop(false)
      +  end
      +
      +  ### 先頭要素にアクセス ###
      +  def peek_first
      +    raise IndexError, '両端キューは空です' if is_empty?
      +
      +    @front.val
      +  end
      +
      +  ### キュー末尾要素を参照 ###
      +  def peek_last
      +    raise IndexError, '両端キューは空です' if is_empty?
      +
      +    @rear.val
      +  end
      +
      +  ### 表示用の配列を返す ###
      +  def to_array
      +    node = @front
      +    res = Array.new(size, 0)
      +    for i in 0...size
      +      res[i] = node.val
      +      node = node.next
      +    end
      +    res
      +  end
      +end
       

      2.   配列に基づく実装

      -

      下図に示すように、配列でキューを実装するのと同様に、循環配列を使って両端キューを実装することもできます。

      -
      +

      次の図に示すように、配列によるキュー実装と同様に、循環配列を使って両端キューを実装することもできます。

      +
      -

      配列による両端キューのエンキューとデキュー操作の実装

      +

      配列による両端キューのエンキューとデキュー

      array_deque_push_last

      @@ -5269,359 +6581,1415 @@
      -

      図 5-9   配列による両端キューのエンキューとデキュー操作の実装

      +

      図 5-9   配列による両端キューのエンキューとデキュー

      -

      実装では「前端エンキュー」と「後端デキュー」のメソッドを追加するだけです:

      +

      キュー実装を土台として、「先頭へのエンキュー」と「末尾からのデキュー」のメソッドを追加するだけで済みます:

      -
      array_deque.py
      class ArrayDeque:
      -    """循環配列ベースの双端キュークラス"""
      -
      -    def __init__(self, capacity: int):
      -        """コンストラクタ"""
      -        self._nums: list[int] = [0] * capacity
      -        self._front: int = 0
      -        self._size: int = 0
      -
      -    def capacity(self) -> int:
      -        """双端キューの容量を取得"""
      -        return len(self._nums)
      -
      -    def size(self) -> int:
      -        """双端キューの長さを取得"""
      -        return self._size
      -
      -    def is_empty(self) -> bool:
      -        """双端キューが空かどうかを判定"""
      -        return self._size == 0
      -
      -    def index(self, i: int) -> int:
      -        """循環配列のインデックスを計算"""
      -        # モジュロ演算によって循環配列を実装
      -        # i が配列の末尾を超えた場合、先頭に戻る
      -        # i が配列の先頭を超えた場合、末尾に戻る
      -        return (i + self.capacity()) % self.capacity()
      -
      -    def push_first(self, num: int):
      -        """前端エンキュー"""
      -        if self._size == self.capacity():
      -            print("双端キューが満杯です")
      -            return
      -        # フロントポインタを左に1つ移動
      -        # モジュロ演算によってフロントが配列の先頭を超えて末尾に戻ることを実装
      -        self._front = self.index(self._front - 1)
      -        # num を前端に追加
      -        self._nums[self._front] = num
      -        self._size += 1
      -
      -    def push_last(self, num: int):
      -        """後端エンキュー"""
      -        if self._size == self.capacity():
      -            print("双端キューが満杯です")
      -            return
      -        # リアポインタを計算、リアインデックス + 1 を指す
      -        rear = self.index(self._front + self._size)
      -        # num を後端に追加
      -        self._nums[rear] = num
      -        self._size += 1
      -
      -    def pop_first(self) -> int:
      -        """前端デキュー"""
      -        num = self.peek_first()
      -        # フロントポインタを1つ後ろに移動
      -        self._front = self.index(self._front + 1)
      -        self._size -= 1
      -        return num
      -
      -    def pop_last(self) -> int:
      -        """後端デキュー"""
      -        num = self.peek_last()
      -        self._size -= 1
      -        return num
      -
      -    def peek_first(self) -> int:
      -        """前端要素にアクセス"""
      -        if self.is_empty():
      -            raise IndexError("Double-ended queue is empty")
      -        return self._nums[self._front]
      -
      -    def peek_last(self) -> int:
      -        """後端要素にアクセス"""
      -        if self.is_empty():
      -            raise IndexError("Double-ended queue is empty")
      -        # 後端要素のインデックスを計算
      -        last = self.index(self._front + self._size - 1)
      -        return self._nums[last]
      -
      -    def to_array(self) -> list[int]:
      -        """出力用の配列を返す"""
      -        # 有効な長さ範囲内の要素のみを変換
      -        res = []
      -        for i in range(self._size):
      -            res.append(self._nums[self.index(self._front + i)])
      -        return res
      -
      -
      -
      -
      array_deque.cpp
      /* 循環配列に基づく両端キュークラス */
      -class ArrayDeque {
      -  private:
      -    vector<int> nums; // 両端キューの要素を格納する配列
      -    int front;        // 先頭ポインタ、先頭要素を指す
      -    int queSize;      // 両端キューの長さ
      -
      -  public:
      -    /* コンストラクタ */
      -    ArrayDeque(int capacity) {
      -        nums.resize(capacity);
      -        front = queSize = 0;
      -    }
      -
      -    /* 両端キューの容量を取得 */
      -    int capacity() {
      -        return nums.size();
      -    }
      -
      -    /* 両端キューの長さを取得 */
      -    int size() {
      -        return queSize;
      -    }
      -
      -    /* 両端キューが空かどうかを判定 */
      -    bool isEmpty() {
      -        return queSize == 0;
      -    }
      -
      -    /* 循環配列のインデックスを計算 */
      -    int index(int i) {
      -        // 剰余演算で循環配列を実現
      -        // iが配列の末尾を超えた場合、先頭に戻る
      -        // iが配列の先頭を超えた場合、末尾に戻る
      -        return (i + capacity()) % capacity();
      -    }
      -
      -    /* 先頭エンキュー */
      -    void pushFirst(int num) {
      -        if (queSize == capacity()) {
      -            cout << "Double-ended queue is full" << endl;
      -            return;
      -        }
      -        // 先頭ポインタを1つ左に移動
      -        // 剰余演算でfrontが配列の先頭を越えて末尾に戻ることを実現
      -        front = index(front - 1);
      -        // numを先頭に追加
      -        nums[front] = num;
      -        queSize++;
      -    }
      +
      array_deque.py
      class ArrayDeque:
      +    """循環配列ベースの両端キュー"""
      +
      +    def __init__(self, capacity: int):
      +        """コンストラクタ"""
      +        self._nums: list[int] = [0] * capacity
      +        self._front: int = 0
      +        self._size: int = 0
      +
      +    def capacity(self) -> int:
      +        """両端キューの容量を取得"""
      +        return len(self._nums)
      +
      +    def size(self) -> int:
      +        """両端キューの長さを取得"""
      +        return self._size
      +
      +    def is_empty(self) -> bool:
      +        """両端キューが空かどうかを判定"""
      +        return self._size == 0
      +
      +    def index(self, i: int) -> int:
      +        """循環配列のインデックスを計算"""
      +        # 剰余演算により配列の先頭と末尾をつなげる
      +        # i が配列の末尾を越えたら先頭に戻る
      +        # i が配列の先頭を越えて前に出たら末尾に戻る
      +        return (i + self.capacity()) % self.capacity()
      +
      +    def push_first(self, num: int):
      +        """キュー先頭にエンキュー"""
      +        if self._size == self.capacity():
      +            print("両端キューがいっぱいです")
      +            return
      +        # 先頭ポインタを左に 1 つ移動する
      +        # 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする
      +        self._front = self.index(self._front - 1)
      +        # num をキュー先頭に追加
      +        self._nums[self._front] = num
      +        self._size += 1
      +
      +    def push_last(self, num: int):
      +        """キュー末尾にエンキュー"""
      +        if self._size == self.capacity():
      +            print("両端キューがいっぱいです")
      +            return
      +        # キュー末尾ポインタを計算し、末尾インデックス + 1 を指す
      +        rear = self.index(self._front + self._size)
      +        # num をキュー末尾に追加
      +        self._nums[rear] = num
      +        self._size += 1
       
      -    /* 末尾エンキュー */
      -    void pushLast(int num) {
      -        if (queSize == capacity()) {
      -            cout << "Double-ended queue is full" << endl;
      -            return;
      -        }
      -        // 末尾ポインタを計算、末尾インデックス + 1を指す
      -        int rear = index(front + queSize);
      -        // numを末尾に追加
      -        nums[rear] = num;
      -        queSize++;
      -    }
      -
      -    /* 先頭デキュー */
      -    int popFirst() {
      -        int num = peekFirst();
      -        // 先頭ポインタを1つ後ろに移動
      -        front = index(front + 1);
      -        queSize--;
      -        return num;
      -    }
      -
      -    /* 末尾デキュー */
      -    int popLast() {
      -        int num = peekLast();
      -        queSize--;
      -        return num;
      -    }
      -
      -    /* 先頭要素にアクセス */
      -    int peekFirst() {
      -        if (isEmpty())
      -            throw out_of_range("Double-ended queue is empty");
      -        return nums[front];
      -    }
      -
      -    /* 末尾要素にアクセス */
      -    int peekLast() {
      -        if (isEmpty())
      -            throw out_of_range("Double-ended queue is empty");
      -        // 末尾要素のインデックスを計算
      -        int last = index(front + queSize - 1);
      -        return nums[last];
      -    }
      -
      -    /* 印刷用に配列を返却 */
      -    vector<int> toVector() {
      -        // 有効な長さ範囲内の要素のみを変換
      -        vector<int> res(queSize);
      -        for (int i = 0, j = front; i < queSize; i++, j++) {
      -            res[i] = nums[index(j)];
      -        }
      -        return res;
      -    }
      -};
      +    def pop_first(self) -> int:
      +        """キュー先頭からデキュー"""
      +        num = self.peek_first()
      +        # 先頭ポインタを 1 つ後ろへ進める
      +        self._front = self.index(self._front + 1)
      +        self._size -= 1
      +        return num
      +
      +    def pop_last(self) -> int:
      +        """キュー末尾からデキュー"""
      +        num = self.peek_last()
      +        self._size -= 1
      +        return num
      +
      +    def peek_first(self) -> int:
      +        """キュー先頭の要素にアクセス"""
      +        if self.is_empty():
      +            raise IndexError("両端キューが空です")
      +        return self._nums[self._front]
      +
      +    def peek_last(self) -> int:
      +        """キュー末尾の要素にアクセス"""
      +        if self.is_empty():
      +            raise IndexError("両端キューが空です")
      +        # 末尾要素のインデックスを計算
      +        last = self.index(self._front + self._size - 1)
      +        return self._nums[last]
      +
      +    def to_array(self) -> list[int]:
      +        """出力用の配列を返す"""
      +        # 有効長の範囲内のリスト要素のみを変換
      +        res = []
      +        for i in range(self._size):
      +            res.append(self._nums[self.index(self._front + i)])
      +        return res
       
      -
      array_deque.java
      /* 循環配列に基づく両端キュークラス */
      -class ArrayDeque {
      -    private int[] nums; // 両端キューの要素を格納する配列
      -    private int front; // 先頭ポインタ、先頭要素を指す
      -    private int queSize; // 両端キューの長さ
      -
      -    /* コンストラクタ */
      -    public ArrayDeque(int capacity) {
      -        this.nums = new int[capacity];
      -        front = queSize = 0;
      -    }
      -
      -    /* 両端キューの容量を取得 */
      -    public int capacity() {
      -        return nums.length;
      -    }
      -
      -    /* 両端キューの長さを取得 */
      -    public int size() {
      -        return queSize;
      -    }
      -
      -    /* 両端キューが空かどうかを判定 */
      -    public boolean isEmpty() {
      -        return queSize == 0;
      -    }
      -
      -    /* 循環配列インデックスを計算 */
      -    private int index(int i) {
      -        // モジュロ演算により循環配列を実装
      -        // i が配列の末尾を超える場合、先頭に戻る
      -        // i が配列の先頭を超える場合、末尾に戻る
      -        return (i + capacity()) % capacity();
      -    }
      -
      -    /* 先頭エンキュー */
      -    public void pushFirst(int num) {
      -        if (queSize == capacity()) {
      -            System.out.println("両端キューが満杯です");
      -            return;
      -        }
      -        // 先頭ポインタを左に移動し、境界を越える場合は配列の末尾に回る
      -        front = index(front - 1);
      -        // 先頭に num を追加
      -        nums[front] = num;
      -        queSize++;
      -    }
      -
      -    /* 末尾エンキュー */
      -    public void pushLast(int num) {
      -        if (queSize == capacity()) {
      -            System.out.println("両端キューが満杯です");
      -            return;
      -        }
      -        // 末尾ポインタを計算し、末尾に要素を追加
      -        int rear = index(front + queSize);
      -        nums[rear] = num;
      -        queSize++;
      -    }
      -
      -    /* 先頭デキュー */
      -    public int popFirst() {
      -        int num = peekFirst();
      -        // 先頭ポインタを右に移動
      -        front = index(front + 1);
      -        queSize--;
      -        return num;
      -    }
      -
      -    /* 末尾デキュー */
      -    public int popLast() {
      -        int num = peekLast();
      -        queSize--;
      -        return num;
      -    }
      -
      -    /* 先頭要素にアクセス */
      -    public int peekFirst() {
      -        if (isEmpty())
      -            throw new IndexOutOfBoundsException();
      -        return nums[front];
      -    }
      -
      -    /* 末尾要素にアクセス */
      -    public int peekLast() {
      -        if (isEmpty())
      -            throw new IndexOutOfBoundsException();
      -        // 末尾要素のインデックスを計算
      -        int last = index(front + queSize - 1);
      -        return nums[last];
      -    }
      -
      -    /* 配列を返す */
      -    public int[] toArray() {
      -        // front から開始して queSize 個の要素のみをコピー
      -        int[] res = new int[queSize];
      -        for (int i = 0, j = front; i < queSize; i++, j++) {
      -            res[i] = nums[index(j)];
      -        }
      -        return res;
      -    }
      -}
      +
      array_deque.cpp
      /* 循環配列ベースの両端キュー */
      +class ArrayDeque {
      +  private:
      +    vector<int> nums; // 両端キューの要素を格納する配列
      +    int front;        // 先頭ポインタ。先頭要素を指す
      +    int queSize;      // 両端キューの長さ
      +
      +  public:
      +    /* コンストラクタ */
      +    ArrayDeque(int capacity) {
      +        nums.resize(capacity);
      +        front = queSize = 0;
      +    }
      +
      +    /* 両端キューの容量を取得 */
      +    int capacity() {
      +        return nums.size();
      +    }
      +
      +    /* 両端キューの長さを取得 */
      +    int size() {
      +        return queSize;
      +    }
      +
      +    /* 両端キューが空かどうかを判定 */
      +    bool isEmpty() {
      +        return queSize == 0;
      +    }
      +
      +    /* 循環配列のインデックスを計算 */
      +    int index(int i) {
      +        // 剰余演算により配列の先頭と末尾をつなげる
      +        // i が配列の末尾を越えたら先頭に戻る
      +        // i が配列の先頭を越えて前に出たら末尾に戻る
      +        return (i + capacity()) % capacity();
      +    }
      +
      +    /* キュー先頭にエンキュー */
      +    void pushFirst(int num) {
      +        if (queSize == capacity()) {
      +            cout << "両端キューがいっぱいです" << endl;
      +            return;
      +        }
      +        // 先頭ポインタを左に 1 つ移動する
      +        // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする
      +        front = index(front - 1);
      +        // num をキュー先頭に追加
      +        nums[front] = num;
      +        queSize++;
      +    }
      +
      +    /* キュー末尾にエンキュー */
      +    void pushLast(int num) {
      +        if (queSize == capacity()) {
      +            cout << "両端キューがいっぱいです" << endl;
      +            return;
      +        }
      +        // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す
      +        int rear = index(front + queSize);
      +        // num をキュー末尾に追加
      +        nums[rear] = num;
      +        queSize++;
      +    }
      +
      +    /* キュー先頭からデキュー */
      +    int popFirst() {
      +        int num = peekFirst();
      +        // 先頭ポインタを 1 つ後ろへ進める
      +        front = index(front + 1);
      +        queSize--;
      +        return num;
      +    }
      +
      +    /* キュー末尾からデキュー */
      +    int popLast() {
      +        int num = peekLast();
      +        queSize--;
      +        return num;
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    int peekFirst() {
      +        if (isEmpty())
      +            throw out_of_range("両端キューが空です");
      +        return nums[front];
      +    }
      +
      +    /* キュー末尾の要素にアクセス */
      +    int peekLast() {
      +        if (isEmpty())
      +            throw out_of_range("両端キューが空です");
      +        // 末尾要素のインデックスを計算
      +        int last = index(front + queSize - 1);
      +        return nums[last];
      +    }
      +
      +    /* 出力用の配列を返す */
      +    vector<int> toVector() {
      +        // 有効長の範囲内のリスト要素のみを変換
      +        vector<int> res(queSize);
      +        for (int i = 0, j = front; i < queSize; i++, j++) {
      +            res[i] = nums[index(j)];
      +        }
      +        return res;
      +    }
      +};
       
      -
      array_deque.cs
      [class]{ArrayDeque}-[func]{}
      +
      array_deque.java
      /* 循環配列ベースの両端キュー */
      +class ArrayDeque {
      +    private int[] nums; // 両端キューの要素を格納する配列
      +    private int front; // 先頭ポインタ。先頭要素を指す
      +    private int queSize; // 両端キューの長さ
      +
      +    /* コンストラクタ */
      +    public ArrayDeque(int capacity) {
      +        this.nums = new int[capacity];
      +        front = queSize = 0;
      +    }
      +
      +    /* 両端キューの容量を取得 */
      +    public int capacity() {
      +        return nums.length;
      +    }
      +
      +    /* 両端キューの長さを取得 */
      +    public int size() {
      +        return queSize;
      +    }
      +
      +    /* 両端キューが空かどうかを判定 */
      +    public boolean isEmpty() {
      +        return queSize == 0;
      +    }
      +
      +    /* 循環配列のインデックスを計算 */
      +    private int index(int i) {
      +        // 剰余演算により配列の先頭と末尾をつなげる
      +        // i が配列の末尾を越えたら先頭に戻る
      +        // i が配列の先頭を越えて前に出たら末尾に戻る
      +        return (i + capacity()) % capacity();
      +    }
      +
      +    /* キュー先頭にエンキュー */
      +    public void pushFirst(int num) {
      +        if (queSize == capacity()) {
      +            System.out.println("双方向キューは満杯です");
      +            return;
      +        }
      +        // 先頭ポインタを左に 1 つ移動する
      +        // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする
      +        front = index(front - 1);
      +        // num をキュー先頭に追加
      +        nums[front] = num;
      +        queSize++;
      +    }
      +
      +    /* キュー末尾にエンキュー */
      +    public void pushLast(int num) {
      +        if (queSize == capacity()) {
      +            System.out.println("双方向キューは満杯です");
      +            return;
      +        }
      +        // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す
      +        int rear = index(front + queSize);
      +        // num をキュー末尾に追加
      +        nums[rear] = num;
      +        queSize++;
      +    }
      +
      +    /* キュー先頭からデキュー */
      +    public int popFirst() {
      +        int num = peekFirst();
      +        // 先頭ポインタを 1 つ後ろへ進める
      +        front = index(front + 1);
      +        queSize--;
      +        return num;
      +    }
      +
      +    /* キュー末尾からデキュー */
      +    public int popLast() {
      +        int num = peekLast();
      +        queSize--;
      +        return num;
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    public int peekFirst() {
      +        if (isEmpty())
      +            throw new IndexOutOfBoundsException();
      +        return nums[front];
      +    }
      +
      +    /* キュー末尾の要素にアクセス */
      +    public int peekLast() {
      +        if (isEmpty())
      +            throw new IndexOutOfBoundsException();
      +        // 末尾要素のインデックスを計算
      +        int last = index(front + queSize - 1);
      +        return nums[last];
      +    }
      +
      +    /* 出力用の配列を返す */
      +    public int[] toArray() {
      +        // 有効長の範囲内のリスト要素のみを変換
      +        int[] res = new int[queSize];
      +        for (int i = 0, j = front; i < queSize; i++, j++) {
      +            res[i] = nums[index(j)];
      +        }
      +        return res;
      +    }
      +}
       
      -
      array_deque.go
      [class]{arrayDeque}-[func]{}
      +
      array_deque.cs
      /* 循環配列ベースの両端キュー */
      +class ArrayDeque {
      +    int[] nums;  // 両端キューの要素を格納する配列
      +    int front;   // 先頭ポインタ。先頭要素を指す
      +    int queSize; // 両端キューの長さ
      +
      +    /* コンストラクタ */
      +    public ArrayDeque(int capacity) {
      +        nums = new int[capacity];
      +        front = queSize = 0;
      +    }
      +
      +    /* 両端キューの容量を取得 */
      +    int Capacity() {
      +        return nums.Length;
      +    }
      +
      +    /* 両端キューの長さを取得 */
      +    public int Size() {
      +        return queSize;
      +    }
      +
      +    /* 両端キューが空かどうかを判定 */
      +    public bool IsEmpty() {
      +        return queSize == 0;
      +    }
      +
      +    /* 循環配列のインデックスを計算 */
      +    int Index(int i) {
      +        // 剰余演算により配列の先頭と末尾をつなげる
      +        // i が配列の末尾を越えたら先頭に戻る
      +        // i が配列の先頭を越えて前に出たら末尾に戻る
      +        return (i + Capacity()) % Capacity();
      +    }
      +
      +    /* キュー先頭にエンキュー */
      +    public void PushFirst(int num) {
      +        if (queSize == Capacity()) {
      +            Console.WriteLine("両端キューは満杯です");
      +            return;
      +        }
      +        // 先頭ポインタを左に 1 つ移動する
      +        // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする
      +        front = Index(front - 1);
      +        // num をキュー先頭に追加
      +        nums[front] = num;
      +        queSize++;
      +    }
      +
      +    /* キュー末尾にエンキュー */
      +    public void PushLast(int num) {
      +        if (queSize == Capacity()) {
      +            Console.WriteLine("両端キューは満杯です");
      +            return;
      +        }
      +        // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す
      +        int rear = Index(front + queSize);
      +        // num をキュー末尾に追加
      +        nums[rear] = num;
      +        queSize++;
      +    }
      +
      +    /* キュー先頭からデキュー */
      +    public int PopFirst() {
      +        int num = PeekFirst();
      +        // 先頭ポインタを 1 つ後ろへ進める
      +        front = Index(front + 1);
      +        queSize--;
      +        return num;
      +    }
      +
      +    /* キュー末尾からデキュー */
      +    public int PopLast() {
      +        int num = PeekLast();
      +        queSize--;
      +        return num;
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    public int PeekFirst() {
      +        if (IsEmpty()) {
      +            throw new InvalidOperationException();
      +        }
      +        return nums[front];
      +    }
      +
      +    /* キュー末尾の要素にアクセス */
      +    public int PeekLast() {
      +        if (IsEmpty()) {
      +            throw new InvalidOperationException();
      +        }
      +        // 末尾要素のインデックスを計算
      +        int last = Index(front + queSize - 1);
      +        return nums[last];
      +    }
      +
      +    /* 出力用の配列を返す */
      +    public int[] ToArray() {
      +        // 有効長の範囲内のリスト要素のみを変換
      +        int[] res = new int[queSize];
      +        for (int i = 0, j = front; i < queSize; i++, j++) {
      +            res[i] = nums[Index(j)];
      +        }
      +        return res;
      +    }
      +}
       
      -
      array_deque.swift
      [class]{ArrayDeque}-[func]{}
      +
      array_deque.go
      /* 循環配列ベースの両端キュー */
      +type arrayDeque struct {
      +    nums        []int // 両端キューの要素を格納する配列
      +    front       int   // 先頭ポインタ。先頭要素を指す
      +    queSize     int   // 両端キューの長さ
      +    queCapacity int   // キュー容量(格納できる要素数の上限)
      +}
      +
      +/* キューを初期化 */
      +func newArrayDeque(queCapacity int) *arrayDeque {
      +    return &arrayDeque{
      +        nums:        make([]int, queCapacity),
      +        queCapacity: queCapacity,
      +        front:       0,
      +        queSize:     0,
      +    }
      +}
      +
      +/* 両端キューの長さを取得 */
      +func (q *arrayDeque) size() int {
      +    return q.queSize
      +}
      +
      +/* 両端キューが空かどうかを判定 */
      +func (q *arrayDeque) isEmpty() bool {
      +    return q.queSize == 0
      +}
      +
      +/* 循環配列のインデックスを計算 */
      +func (q *arrayDeque) index(i int) int {
      +    // 剰余演算により配列の先頭と末尾をつなげる
      +    // i が配列の末尾を越えたら先頭に戻る
      +    // i が配列の先頭を越えて前に出たら末尾に戻る
      +    return (i + q.queCapacity) % q.queCapacity
      +}
      +
      +/* キュー先頭にエンキュー */
      +func (q *arrayDeque) pushFirst(num int) {
      +    if q.queSize == q.queCapacity {
      +        fmt.Println("両端キューは満杯です")
      +        return
      +    }
      +    // 先頭ポインタを左に 1 つ移動する
      +    // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする
      +    q.front = q.index(q.front - 1)
      +    // num をキュー先頭に追加
      +    q.nums[q.front] = num
      +    q.queSize++
      +}
      +
      +/* キュー末尾にエンキュー */
      +func (q *arrayDeque) pushLast(num int) {
      +    if q.queSize == q.queCapacity {
      +        fmt.Println("両端キューは満杯です")
      +        return
      +    }
      +    // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す
      +    rear := q.index(q.front + q.queSize)
      +    // num をキュー末尾に追加
      +    q.nums[rear] = num
      +    q.queSize++
      +}
      +
      +/* キュー先頭からデキュー */
      +func (q *arrayDeque) popFirst() any {
      +    num := q.peekFirst()
      +    if num == nil {
      +        return nil
      +    }
      +    // 先頭ポインタを 1 つ後ろへ進める
      +    q.front = q.index(q.front + 1)
      +    q.queSize--
      +    return num
      +}
      +
      +/* キュー末尾からデキュー */
      +func (q *arrayDeque) popLast() any {
      +    num := q.peekLast()
      +    if num == nil {
      +        return nil
      +    }
      +    q.queSize--
      +    return num
      +}
      +
      +/* キュー先頭の要素にアクセス */
      +func (q *arrayDeque) peekFirst() any {
      +    if q.isEmpty() {
      +        return nil
      +    }
      +    return q.nums[q.front]
      +}
      +
      +/* キュー末尾の要素にアクセス */
      +func (q *arrayDeque) peekLast() any {
      +    if q.isEmpty() {
      +        return nil
      +    }
      +    // 末尾要素のインデックスを計算
      +    last := q.index(q.front + q.queSize - 1)
      +    return q.nums[last]
      +}
      +
      +/* 表示用に Slice を取得 */
      +func (q *arrayDeque) toSlice() []int {
      +    // 有効長の範囲内のリスト要素のみを変換
      +    res := make([]int, q.queSize)
      +    for i, j := 0, q.front; i < q.queSize; i++ {
      +        res[i] = q.nums[q.index(j)]
      +        j++
      +    }
      +    return res
      +}
       
      -
      array_deque.js
      [class]{ArrayDeque}-[func]{}
      +
      array_deque.swift
      /* 循環配列ベースの両端キュー */
      +class ArrayDeque {
      +    private var nums: [Int] // 両端キューの要素を格納する配列
      +    private var front: Int // 先頭ポインタ。先頭要素を指す
      +    private var _size: Int // 両端キューの長さ
      +
      +    /* コンストラクタ */
      +    init(capacity: Int) {
      +        nums = Array(repeating: 0, count: capacity)
      +        front = 0
      +        _size = 0
      +    }
      +
      +    /* 両端キューの容量を取得 */
      +    func capacity() -> Int {
      +        nums.count
      +    }
      +
      +    /* 両端キューの長さを取得 */
      +    func size() -> Int {
      +        _size
      +    }
      +
      +    /* 両端キューが空かどうかを判定 */
      +    func isEmpty() -> Bool {
      +        size() == 0
      +    }
      +
      +    /* 循環配列のインデックスを計算 */
      +    private func index(i: Int) -> Int {
      +        // 剰余演算により配列の先頭と末尾をつなげる
      +        // i が配列の末尾を越えたら先頭に戻る
      +        // i が配列の先頭を越えて前に出たら末尾に戻る
      +        (i + capacity()) % capacity()
      +    }
      +
      +    /* キュー先頭にエンキュー */
      +    func pushFirst(num: Int) {
      +        if size() == capacity() {
      +            print("両端キューがいっぱいです")
      +            return
      +        }
      +        // 先頭ポインタを左に 1 つ移動する
      +        // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする
      +        front = index(i: front - 1)
      +        // num をキュー先頭に追加
      +        nums[front] = num
      +        _size += 1
      +    }
      +
      +    /* キュー末尾にエンキュー */
      +    func pushLast(num: Int) {
      +        if size() == capacity() {
      +            print("両端キューがいっぱいです")
      +            return
      +        }
      +        // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す
      +        let rear = index(i: front + size())
      +        // num をキュー末尾に追加
      +        nums[rear] = num
      +        _size += 1
      +    }
      +
      +    /* キュー先頭からデキュー */
      +    func popFirst() -> Int {
      +        let num = peekFirst()
      +        // 先頭ポインタを 1 つ後ろへ進める
      +        front = index(i: front + 1)
      +        _size -= 1
      +        return num
      +    }
      +
      +    /* キュー末尾からデキュー */
      +    func popLast() -> Int {
      +        let num = peekLast()
      +        _size -= 1
      +        return num
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    func peekFirst() -> Int {
      +        if isEmpty() {
      +            fatalError("両端キューが空です")
      +        }
      +        return nums[front]
      +    }
      +
      +    /* キュー末尾の要素にアクセス */
      +    func peekLast() -> Int {
      +        if isEmpty() {
      +            fatalError("両端キューが空です")
      +        }
      +        // 末尾要素のインデックスを計算
      +        let last = index(i: front + size() - 1)
      +        return nums[last]
      +    }
      +
      +    /* 出力用の配列を返す */
      +    func toArray() -> [Int] {
      +        // 有効長の範囲内のリスト要素のみを変換
      +        (front ..< front + size()).map { nums[index(i: $0)] }
      +    }
      +}
       
      -
      array_deque.ts
      [class]{ArrayDeque}-[func]{}
      +
      array_deque.js
      /* 循環配列ベースの両端キュー */
      +class ArrayDeque {
      +    #nums; // 両端キューの要素を格納する配列
      +    #front; // 先頭ポインタ。先頭要素を指す
      +    #queSize; // 両端キューの長さ
      +
      +    /* コンストラクタ */
      +    constructor(capacity) {
      +        this.#nums = new Array(capacity);
      +        this.#front = 0;
      +        this.#queSize = 0;
      +    }
      +
      +    /* 両端キューの容量を取得 */
      +    capacity() {
      +        return this.#nums.length;
      +    }
      +
      +    /* 両端キューの長さを取得 */
      +    size() {
      +        return this.#queSize;
      +    }
      +
      +    /* 両端キューが空かどうかを判定 */
      +    isEmpty() {
      +        return this.#queSize === 0;
      +    }
      +
      +    /* 循環配列のインデックスを計算 */
      +    index(i) {
      +        // 剰余演算により配列の先頭と末尾をつなげる
      +        // i が配列の末尾を越えたら先頭に戻る
      +        // i が配列の先頭を越えて前に出たら末尾に戻る
      +        return (i + this.capacity()) % this.capacity();
      +    }
      +
      +    /* キュー先頭にエンキュー */
      +    pushFirst(num) {
      +        if (this.#queSize === this.capacity()) {
      +            console.log('両端キューがいっぱいです');
      +            return;
      +        }
      +        // 先頭ポインタを左に 1 つ移動する
      +        // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする
      +        this.#front = this.index(this.#front - 1);
      +        // num をキュー先頭に追加
      +        this.#nums[this.#front] = num;
      +        this.#queSize++;
      +    }
      +
      +    /* キュー末尾にエンキュー */
      +    pushLast(num) {
      +        if (this.#queSize === this.capacity()) {
      +            console.log('両端キューがいっぱいです');
      +            return;
      +        }
      +        // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す
      +        const rear = this.index(this.#front + this.#queSize);
      +        // num をキュー末尾に追加
      +        this.#nums[rear] = num;
      +        this.#queSize++;
      +    }
      +
      +    /* キュー先頭からデキュー */
      +    popFirst() {
      +        const num = this.peekFirst();
      +        // 先頭ポインタを 1 つ後ろへ進める
      +        this.#front = this.index(this.#front + 1);
      +        this.#queSize--;
      +        return num;
      +    }
      +
      +    /* キュー末尾からデキュー */
      +    popLast() {
      +        const num = this.peekLast();
      +        this.#queSize--;
      +        return num;
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    peekFirst() {
      +        if (this.isEmpty()) throw new Error('The Deque Is Empty.');
      +        return this.#nums[this.#front];
      +    }
      +
      +    /* キュー末尾の要素にアクセス */
      +    peekLast() {
      +        if (this.isEmpty()) throw new Error('The Deque Is Empty.');
      +        // 末尾要素のインデックスを計算
      +        const last = this.index(this.#front + this.#queSize - 1);
      +        return this.#nums[last];
      +    }
      +
      +    /* 出力用の配列を返す */
      +    toArray() {
      +        // 有効長の範囲内のリスト要素のみを変換
      +        const res = [];
      +        for (let i = 0, j = this.#front; i < this.#queSize; i++, j++) {
      +            res[i] = this.#nums[this.index(j)];
      +        }
      +        return res;
      +    }
      +}
       
      -
      array_deque.dart
      [class]{ArrayDeque}-[func]{}
      +
      array_deque.ts
      /* 循環配列ベースの両端キュー */
      +class ArrayDeque {
      +    private nums: number[]; // 両端キューの要素を格納する配列
      +    private front: number; // 先頭ポインタ。先頭要素を指す
      +    private queSize: number; // 両端キューの長さ
      +
      +    /* コンストラクタ */
      +    constructor(capacity: number) {
      +        this.nums = new Array(capacity);
      +        this.front = 0;
      +        this.queSize = 0;
      +    }
      +
      +    /* 両端キューの容量を取得 */
      +    capacity(): number {
      +        return this.nums.length;
      +    }
      +
      +    /* 両端キューの長さを取得 */
      +    size(): number {
      +        return this.queSize;
      +    }
      +
      +    /* 両端キューが空かどうかを判定 */
      +    isEmpty(): boolean {
      +        return this.queSize === 0;
      +    }
      +
      +    /* 循環配列のインデックスを計算 */
      +    index(i: number): number {
      +        // 剰余演算により配列の先頭と末尾をつなげる
      +        // i が配列の末尾を越えたら先頭に戻る
      +        // i が配列の先頭を越えて前に出たら末尾に戻る
      +        return (i + this.capacity()) % this.capacity();
      +    }
      +
      +    /* キュー先頭にエンキュー */
      +    pushFirst(num: number): void {
      +        if (this.queSize === this.capacity()) {
      +            console.log('両端キューは満杯です');
      +            return;
      +        }
      +        // 先頭ポインタを左に 1 つ移動する
      +        // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする
      +        this.front = this.index(this.front - 1);
      +        // num をキュー先頭に追加
      +        this.nums[this.front] = num;
      +        this.queSize++;
      +    }
      +
      +    /* キュー末尾にエンキュー */
      +    pushLast(num: number): void {
      +        if (this.queSize === this.capacity()) {
      +            console.log('両端キューは満杯です');
      +            return;
      +        }
      +        // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す
      +        const rear: number = this.index(this.front + this.queSize);
      +        // num をキュー末尾に追加
      +        this.nums[rear] = num;
      +        this.queSize++;
      +    }
      +
      +    /* キュー先頭からデキュー */
      +    popFirst(): number {
      +        const num: number = this.peekFirst();
      +        // 先頭ポインタを 1 つ後ろへ進める
      +        this.front = this.index(this.front + 1);
      +        this.queSize--;
      +        return num;
      +    }
      +
      +    /* キュー末尾からデキュー */
      +    popLast(): number {
      +        const num: number = this.peekLast();
      +        this.queSize--;
      +        return num;
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    peekFirst(): number {
      +        if (this.isEmpty()) throw new Error('The Deque Is Empty.');
      +        return this.nums[this.front];
      +    }
      +
      +    /* キュー末尾の要素にアクセス */
      +    peekLast(): number {
      +        if (this.isEmpty()) throw new Error('The Deque Is Empty.');
      +        // 末尾要素のインデックスを計算
      +        const last = this.index(this.front + this.queSize - 1);
      +        return this.nums[last];
      +    }
      +
      +    /* 出力用の配列を返す */
      +    toArray(): number[] {
      +        // 有効長の範囲内のリスト要素のみを変換
      +        const res: number[] = [];
      +        for (let i = 0, j = this.front; i < this.queSize; i++, j++) {
      +            res[i] = this.nums[this.index(j)];
      +        }
      +        return res;
      +    }
      +}
       
      -
      array_deque.rs
      [class]{ArrayDeque}-[func]{}
      +
      array_deque.dart
      /* 循環配列ベースの両端キュー */
      +class ArrayDeque {
      +  late List<int> _nums; // 両端キューの要素を格納する配列
      +  late int _front; // 先頭ポインタ。先頭要素を指す
      +  late int _queSize; // 両端キューの長さ
      +
      +  /* コンストラクタ */
      +  ArrayDeque(int capacity) {
      +    this._nums = List.filled(capacity, 0);
      +    this._front = this._queSize = 0;
      +  }
      +
      +  /* 両端キューの容量を取得 */
      +  int capacity() {
      +    return _nums.length;
      +  }
      +
      +  /* 両端キューの長さを取得 */
      +  int size() {
      +    return _queSize;
      +  }
      +
      +  /* 両端キューが空かどうかを判定 */
      +  bool isEmpty() {
      +    return _queSize == 0;
      +  }
      +
      +  /* 循環配列のインデックスを計算 */
      +  int index(int i) {
      +    // 剰余演算により配列の先頭と末尾をつなげる
      +    // i が配列の末尾を越えたら先頭に戻る
      +    // i が配列の先頭を越えて前に出たら末尾に戻る
      +    return (i + capacity()) % capacity();
      +  }
      +
      +  /* キュー先頭にエンキュー */
      +  void pushFirst(int _num) {
      +    if (_queSize == capacity()) {
      +      throw Exception("両端キューがいっぱいです");
      +    }
      +    // 先頭ポインタを左に 1 つ移動する
      +    // 剰余演算により _front が配列の先頭を越えたあと末尾に戻るようにする
      +    _front = index(_front - 1);
      +    // _num をキューの先頭に追加
      +    _nums[_front] = _num;
      +    _queSize++;
      +  }
      +
      +  /* キュー末尾にエンキュー */
      +  void pushLast(int _num) {
      +    if (_queSize == capacity()) {
      +      throw Exception("両端キューがいっぱいです");
      +    }
      +    // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す
      +    int rear = index(_front + _queSize);
      +    // _num をキュー末尾に追加
      +    _nums[rear] = _num;
      +    _queSize++;
      +  }
      +
      +  /* キュー先頭からデキュー */
      +  int popFirst() {
      +    int _num = peekFirst();
      +    // 先頭ポインタを右に 1 つ移動する
      +    _front = index(_front + 1);
      +    _queSize--;
      +    return _num;
      +  }
      +
      +  /* キュー末尾からデキュー */
      +  int popLast() {
      +    int _num = peekLast();
      +    _queSize--;
      +    return _num;
      +  }
      +
      +  /* キュー先頭の要素にアクセス */
      +  int peekFirst() {
      +    if (isEmpty()) {
      +      throw Exception("両端キューが空です");
      +    }
      +    return _nums[_front];
      +  }
      +
      +  /* キュー末尾の要素にアクセス */
      +  int peekLast() {
      +    if (isEmpty()) {
      +      throw Exception("両端キューが空です");
      +    }
      +    // 末尾要素のインデックスを計算
      +    int last = index(_front + _queSize - 1);
      +    return _nums[last];
      +  }
      +
      +  /* 出力用の配列を返す */
      +  List<int> toArray() {
      +    // 有効長の範囲内のリスト要素のみを変換
      +    List<int> res = List.filled(_queSize, 0);
      +    for (int i = 0, j = _front; i < _queSize; i++, j++) {
      +      res[i] = _nums[index(j)];
      +    }
      +    return res;
      +  }
      +}
       
      -
      array_deque.c
      [class]{ArrayDeque}-[func]{}
      +
      array_deque.rs
      /* 循環配列ベースの両端キュー */
      +struct ArrayDeque<T> {
      +    nums: Vec<T>,    // 両端キューの要素を格納する配列
      +    front: usize,    // 先頭ポインタ。先頭要素を指す
      +    que_size: usize, // 両端キューの長さ
      +}
      +
      +impl<T: Copy + Default> ArrayDeque<T> {
      +    /* コンストラクタ */
      +    pub fn new(capacity: usize) -> Self {
      +        Self {
      +            nums: vec![T::default(); capacity],
      +            front: 0,
      +            que_size: 0,
      +        }
      +    }
      +
      +    /* 両端キューの容量を取得 */
      +    pub fn capacity(&self) -> usize {
      +        self.nums.len()
      +    }
      +
      +    /* 両端キューの長さを取得 */
      +    pub fn size(&self) -> usize {
      +        self.que_size
      +    }
      +
      +    /* 両端キューが空かどうかを判定 */
      +    pub fn is_empty(&self) -> bool {
      +        self.que_size == 0
      +    }
      +
      +    /* 循環配列のインデックスを計算 */
      +    fn index(&self, i: i32) -> usize {
      +        // 剰余演算により配列の先頭と末尾をつなげる
      +        // i が配列の末尾を越えたら先頭に戻る
      +        // i が配列の先頭を越えて前に出たら末尾に戻る
      +        ((i + self.capacity() as i32) % self.capacity() as i32) as usize
      +    }
      +
      +    /* キュー先頭にエンキュー */
      +    pub fn push_first(&mut self, num: T) {
      +        if self.que_size == self.capacity() {
      +            println!("両端キューがいっぱいです");
      +            return;
      +        }
      +        // 先頭ポインタを左に 1 つ移動する
      +        // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする
      +        self.front = self.index(self.front as i32 - 1);
      +        // num をキュー先頭に追加
      +        self.nums[self.front] = num;
      +        self.que_size += 1;
      +    }
      +
      +    /* キュー末尾にエンキュー */
      +    pub fn push_last(&mut self, num: T) {
      +        if self.que_size == self.capacity() {
      +            println!("両端キューがいっぱいです");
      +            return;
      +        }
      +        // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す
      +        let rear = self.index(self.front as i32 + self.que_size as i32);
      +        // num をキュー末尾に追加
      +        self.nums[rear] = num;
      +        self.que_size += 1;
      +    }
      +
      +    /* キュー先頭からデキュー */
      +    fn pop_first(&mut self) -> T {
      +        let num = self.peek_first();
      +        // 先頭ポインタを 1 つ後ろへ進める
      +        self.front = self.index(self.front as i32 + 1);
      +        self.que_size -= 1;
      +        num
      +    }
      +
      +    /* キュー末尾からデキュー */
      +    fn pop_last(&mut self) -> T {
      +        let num = self.peek_last();
      +        self.que_size -= 1;
      +        num
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    fn peek_first(&self) -> T {
      +        if self.is_empty() {
      +            panic!("両端キューが空です")
      +        };
      +        self.nums[self.front]
      +    }
      +
      +    /* キュー末尾の要素にアクセス */
      +    fn peek_last(&self) -> T {
      +        if self.is_empty() {
      +            panic!("両端キューが空です")
      +        };
      +        // 末尾要素のインデックスを計算
      +        let last = self.index(self.front as i32 + self.que_size as i32 - 1);
      +        self.nums[last]
      +    }
      +
      +    /* 出力用の配列を返す */
      +    fn to_array(&self) -> Vec<T> {
      +        // 有効長の範囲内のリスト要素のみを変換
      +        let mut res = vec![T::default(); self.que_size];
      +        let mut j = self.front;
      +        for i in 0..self.que_size {
      +            res[i] = self.nums[self.index(j as i32)];
      +            j += 1;
      +        }
      +        res
      +    }
      +}
       
      -
      array_deque.kt
      [class]{ArrayDeque}-[func]{}
      +
      array_deque.c
      /* 循環配列ベースの両端キュー */
      +typedef struct {
      +    int *nums;       // キュー要素を格納する配列
      +    int front;       // 先頭ポインタ。先頭要素を指す
      +    int queSize;     // 末尾ポインタ。キューの末尾 + 1 を指す
      +    int queCapacity; // キューの容量
      +} ArrayDeque;
      +
      +/* コンストラクタ */
      +ArrayDeque *newArrayDeque(int capacity) {
      +    ArrayDeque *deque = (ArrayDeque *)malloc(sizeof(ArrayDeque));
      +    // 配列を初期化
      +    deque->queCapacity = capacity;
      +    deque->nums = (int *)malloc(sizeof(int) * deque->queCapacity);
      +    deque->front = deque->queSize = 0;
      +    return deque;
      +}
      +
      +/* デストラクタ */
      +void delArrayDeque(ArrayDeque *deque) {
      +    free(deque->nums);
      +    free(deque);
      +}
      +
      +/* 両端キューの容量を取得 */
      +int capacity(ArrayDeque *deque) {
      +    return deque->queCapacity;
      +}
      +
      +/* 両端キューの長さを取得 */
      +int size(ArrayDeque *deque) {
      +    return deque->queSize;
      +}
      +
      +/* 両端キューが空かどうかを判定 */
      +bool empty(ArrayDeque *deque) {
      +    return deque->queSize == 0;
      +}
      +
      +/* 循環配列のインデックスを計算 */
      +int dequeIndex(ArrayDeque *deque, int i) {
      +    // 剰余演算により配列の先頭と末尾をつなげる
      +    // i が配列の末尾を越えたら先頭に戻る
      +    // i が配列の先頭を越えたら末尾に戻る
      +    return ((i + capacity(deque)) % capacity(deque));
      +}
      +
      +/* キュー先頭にエンキュー */
      +void pushFirst(ArrayDeque *deque, int num) {
      +    if (deque->queSize == capacity(deque)) {
      +        printf("両端キューがいっぱいです\r\n");
      +        return;
      +    }
      +    // 先頭ポインタを左に 1 つ移動する
      +    // 剰余演算により front が配列の先頭を越えたあと末尾に戻るようにする
      +    deque->front = dequeIndex(deque, deque->front - 1);
      +    // num をキューの先頭に追加
      +    deque->nums[deque->front] = num;
      +    deque->queSize++;
      +}
      +
      +/* キュー末尾にエンキュー */
      +void pushLast(ArrayDeque *deque, int num) {
      +    if (deque->queSize == capacity(deque)) {
      +        printf("両端キューがいっぱいです\r\n");
      +        return;
      +    }
      +    // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す
      +    int rear = dequeIndex(deque, deque->front + deque->queSize);
      +    // num をキュー末尾に追加
      +    deque->nums[rear] = num;
      +    deque->queSize++;
      +}
      +
      +/* キュー先頭の要素にアクセス */
      +int peekFirst(ArrayDeque *deque) {
      +    // アクセス例外:双方向キューが空です
      +    assert(empty(deque) == 0);
      +    return deque->nums[deque->front];
      +}
      +
      +/* キュー末尾の要素にアクセス */
      +int peekLast(ArrayDeque *deque) {
      +    // アクセス例外:双方向キューが空です
      +    assert(empty(deque) == 0);
      +    int last = dequeIndex(deque, deque->front + deque->queSize - 1);
      +    return deque->nums[last];
      +}
      +
      +/* キュー先頭からデキュー */
      +int popFirst(ArrayDeque *deque) {
      +    int num = peekFirst(deque);
      +    // 先頭ポインタを 1 つ後ろへ進める
      +    deque->front = dequeIndex(deque, deque->front + 1);
      +    deque->queSize--;
      +    return num;
      +}
      +
      +/* キュー末尾からデキュー */
      +int popLast(ArrayDeque *deque) {
      +    int num = peekLast(deque);
      +    deque->queSize--;
      +    return num;
      +}
      +
      +/* 出力用の配列を返す */
      +int *toArray(ArrayDeque *deque, int *queSize) {
      +    *queSize = deque->queSize;
      +    int *res = (int *)calloc(deque->queSize, sizeof(int));
      +    int j = deque->front;
      +    for (int i = 0; i < deque->queSize; i++) {
      +        res[i] = deque->nums[j % deque->queCapacity];
      +        j++;
      +    }
      +    return res;
      +}
       
      -
      array_deque.rb
      [class]{ArrayDeque}-[func]{}
      +
      array_deque.kt
      /* コンストラクタ */
      +class ArrayDeque(capacity: Int) {
      +    private var nums: IntArray = IntArray(capacity) // 両端キューの要素を格納する配列
      +    private var front: Int = 0 // 先頭ポインタ。先頭要素を指す
      +    private var queSize: Int = 0 // 両端キューの長さ
      +
      +    /* 両端キューの容量を取得 */
      +    fun capacity(): Int {
      +        return nums.size
      +    }
      +
      +    /* 両端キューの長さを取得 */
      +    fun size(): Int {
      +        return queSize
      +    }
      +
      +    /* 両端キューが空かどうかを判定 */
      +    fun isEmpty(): Boolean {
      +        return queSize == 0
      +    }
      +
      +    /* 循環配列のインデックスを計算 */
      +    private fun index(i: Int): Int {
      +        // 剰余演算により配列の先頭と末尾をつなげる
      +        // i が配列の末尾を越えたら先頭に戻る
      +        // i が配列の先頭を越えて前に出たら末尾に戻る
      +        return (i + capacity()) % capacity()
      +    }
      +
      +    /* キュー先頭にエンキュー */
      +    fun pushFirst(num: Int) {
      +        if (queSize == capacity()) {
      +            println("双方向キューは満杯です")
      +            return
      +        }
      +        // 先頭ポインタを左に 1 つ移動する
      +        // 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする
      +        front = index(front - 1)
      +        // num をキュー先頭に追加
      +        nums[front] = num
      +        queSize++
      +    }
      +
      +    /* キュー末尾にエンキュー */
      +    fun pushLast(num: Int) {
      +        if (queSize == capacity()) {
      +            println("双方向キューは満杯です")
      +            return
      +        }
      +        // キュー末尾ポインタを計算し、末尾インデックス + 1 を指す
      +        val rear = index(front + queSize)
      +        // num をキュー末尾に追加
      +        nums[rear] = num
      +        queSize++
      +    }
      +
      +    /* キュー先頭からデキュー */
      +    fun popFirst(): Int {
      +        val num = peekFirst()
      +        // 先頭ポインタを 1 つ後ろへ進める
      +        front = index(front + 1)
      +        queSize--
      +        return num
      +    }
      +
      +    /* キュー末尾からデキュー */
      +    fun popLast(): Int {
      +        val num = peekLast()
      +        queSize--
      +        return num
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    fun peekFirst(): Int {
      +        if (isEmpty()) throw IndexOutOfBoundsException()
      +        return nums[front]
      +    }
      +
      +    /* キュー末尾の要素にアクセス */
      +    fun peekLast(): Int {
      +        if (isEmpty()) throw IndexOutOfBoundsException()
      +        // 末尾要素のインデックスを計算
      +        val last = index(front + queSize - 1)
      +        return nums[last]
      +    }
      +
      +    /* 出力用の配列を返す */
      +    fun toArray(): IntArray {
      +        // 有効長の範囲内のリスト要素のみを変換
      +        val res = IntArray(queSize)
      +        var i = 0
      +        var j = front
      +        while (i < queSize) {
      +            res[i] = nums[index(j)]
      +            i++
      +            j++
      +        }
      +        return res
      +    }
      +}
      +
      +
      +
      +
      array_deque.rb
      ### 循環配列で実装した両端キュー ###
      +class ArrayDeque
      +  ### 両端キューの長さを取得 ###
      +  attr_reader :size
      +
      +  ### コンストラクタ ###
      +  def initialize(capacity)
      +    @nums = Array.new(capacity, 0)
      +    @front = 0
      +    @size = 0
      +  end
      +
      +  ### 両端キューの容量を取得 ###
      +  def capacity
      +    @nums.length
      +  end
      +
      +  ### 両端キューが空か判定 ###
      +  def is_empty?
      +    size.zero?
      +  end
      +
      +  ### キュー先頭に追加 ###
      +  def push_first(num)
      +    if size == capacity
      +      puts '両端キューがいっぱいです'
      +      return
      +    end
      +
      +    # 先頭ポインタを左に 1 つ移動する
      +    # 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする
      +    @front = index(@front - 1)
      +    # num をキュー先頭に追加
      +    @nums[@front] = num
      +    @size += 1
      +  end
      +
      +  ### キュー末尾に追加 ###
      +  def push_last(num)
      +    if size == capacity
      +      puts '両端キューがいっぱいです'
      +      return
      +    end
      +
      +    # キュー末尾ポインタを計算し、末尾インデックス + 1 を指す
      +    rear = index(@front + size)
      +    # num をキュー末尾に追加
      +    @nums[rear] = num
      +    @size += 1
      +  end
      +
      +  ### キュー先頭から取り出す ###
      +  def pop_first
      +    num = peek_first
      +    # 先頭ポインタを 1 つ後ろへ進める
      +    @front = index(@front + 1)
      +    @size -= 1
      +    num
      +  end
      +
      +  ### キューの末尾から取り出す ###
      +  def pop_last
      +    num = peek_last
      +    @size -= 1
      +    num
      +  end
      +
      +  ### 先頭要素にアクセス ###
      +  def peek_first
      +    raise IndexError, '両端キューは空です' if is_empty?
      +
      +    @nums[@front]
      +  end
      +
      +  ### キュー末尾要素を参照 ###
      +  def peek_last
      +    raise IndexError, '両端キューは空です' if is_empty?
      +
      +    # 末尾要素のインデックスを計算
      +    last = index(@front + size - 1)
      +    @nums[last]
      +  end
      +
      +  ### 表示用の配列を返す ###
      +  def to_array
      +    # 有効長の範囲内のリスト要素のみを変換
      +    res = []
      +    for i in 0...size
      +      res << @nums[index(@front + i)]
      +    end
      +    res
      +  end
      +
      +  private
      +
      +  ### 循環配列のインデックスを計算 ###
      +  def index(i)
      +    # 剰余演算により配列の先頭と末尾をつなげる
      +    # i が配列の末尾を越えたら先頭に戻る
      +    # i が配列の先頭を越えて前に出たら末尾に戻る
      +    (i + capacity) % capacity
      +  end
      +end
       

      5.3.3   両端キューの応用

      -

      両端キューはスタックとキューの両方のロジックを組み合わせているため、それぞれのすべてのユースケースを実装でき、より大きな柔軟性を提供します

      -

      ソフトウェアの「元に戻す」機能は通常スタックを使って実装されることを知っています:システムは各変更操作をスタックにpushし、次にpopして元に戻すことを実装します。しかし、システムリソースの制限を考慮して、ソフトウェアは元に戻すステップの数を制限することがよくあります(例えば、最後の50ステップのみを許可)。スタックの長さが50を超えた場合、ソフトウェアはスタックの底部(キューの前端)で削除操作を実行する必要があります。しかし、通常のスタックではこの機能を実行できないため、両端キューが必要になります。「元に戻す」のコアロジックは依然としてスタックの後入れ先出し原則に従いますが、両端キューはより柔軟にいくつかの追加ロジックを実装できることに注意してください。

      +

      両端キューはスタックとキューの両方の論理を備えているため、これら 2 つのすべての応用場面を実現でき、さらに高い自由度を提供します

      +

      私たちが知っているように、ソフトウェアの「元に戻す」機能は通常スタックを使って実装されます。システムは変更操作を毎回スタックに push し、その後 pop によって取り消しを実現します。しかし、システム資源の制約を考慮すると、通常ソフトウェアは取り消し可能な手数を制限します(たとえば \(50\) 手まで保存可能)。スタックの長さが \(50\) を超えると、ソフトウェアはスタックの底部(先頭)で削除操作を行う必要があります。しかしスタックではこの機能を実現できないため、この場合はスタックの代わりに両端キューを使用する必要があります。なお、「元に戻す」の中核ロジック自体は依然としてスタックの後入れ先出し原則に従っており、両端キューは追加のロジックをより柔軟に実装できるだけです。

      diff --git a/ja/chapter_stack_and_queue/index.html b/ja/chapter_stack_and_queue/index.html index 6c20fe5e9..f084e8fff 100644 --- a/ja/chapter_stack_and_queue/index.html +++ b/ja/chapter_stack_and_queue/index.html @@ -6,7 +6,7 @@ - + @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1593,7 +1593,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1615,7 +1615,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1637,7 +1637,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1944,7 +1944,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2107,7 +2107,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2493,7 +2493,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2549,7 +2549,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2577,7 +2577,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -3115,7 +3115,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3171,7 +3171,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3199,7 +3199,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3392,7 +3392,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3420,7 +3420,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3561,7 +3561,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3589,7 +3589,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3617,7 +3617,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3645,7 +3645,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3838,7 +3838,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4083,7 +4083,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4277,8 +4277,8 @@

      スタックとキュー

      Abstract

      -

      スタックは積み重ねられた猫のようなもので、キューは一列に並んだ猫のようなものです。

      -

      それらはそれぞれ、後入先出(LIFO)と先入先出(FIFO)の論理関係を表しています。

      +

      スタックは猫を積み重ねるようなもので、キューは猫が列に並ぶようなものです。

      +

      両者はそれぞれ、後入れ先出しと先入れ先出しの論理関係を表します。

      章の内容

        diff --git a/ja/chapter_stack_and_queue/queue/index.html b/ja/chapter_stack_and_queue/queue/index.html index 5c16c8de6..4ac5563b8 100644 --- a/ja/chapter_stack_and_queue/queue/index.html +++ b/ja/chapter_stack_and_queue/queue/index.html @@ -6,7 +6,7 @@ - + @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1531,7 +1531,7 @@ - 5.2.1   キューの一般的な操作 + 5.2.1   キューの基本操作 @@ -1554,7 +1554,7 @@ - 1.   連結リストベースの実装 + 1.   連結リストに基づく実装 @@ -1565,7 +1565,7 @@ - 2.   配列ベースの実装 + 2.   配列に基づく実装 @@ -1702,7 +1702,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1724,7 +1724,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1746,7 +1746,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -2053,7 +2053,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2216,7 +2216,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2602,7 +2602,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2658,7 +2658,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2686,7 +2686,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -3224,7 +3224,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3280,7 +3280,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3308,7 +3308,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3501,7 +3501,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3529,7 +3529,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3670,7 +3670,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3698,7 +3698,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3726,7 +3726,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3754,7 +3754,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3947,7 +3947,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4192,7 +4192,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4338,7 +4338,7 @@ - 5.2.1   キューの一般的な操作 + 5.2.1   キューの基本操作 @@ -4361,7 +4361,7 @@ - 1.   連結リストベースの実装 + 1.   連結リストに基づく実装 @@ -4372,7 +4372,7 @@ - 2.   配列ベースの実装 + 2.   配列に基づく実装 @@ -4433,13 +4433,13 @@

      5.2   キュー

      -

      キューは、先入先出(FIFO)ルールに従う線形データ構造です。名前が示すように、キューは行列の現象をシミュレートし、新参者は列の後ろに並び、前の人が最初に列を離れます。

      -

      下図に示すように、キューの前面を「ヘッド」、後面を「テール」と呼びます。キューの後ろに要素を追加する操作を「エンキュー」、前から要素を削除する操作を「デキュー」と呼びます。

      -

      キューの先入先出ルール

      -

      図 5-4   キューの先入先出ルール

      +

      キュー(queue)は、先入れ先出しの規則に従う線形データ構造です。名前のとおり、キューは順番待ちの現象を模したもので、新しく来た人は絶えずキュー末尾に加わり、キュー先頭にいる人から順に離れていきます。

      +

      下図のように、キューの先頭を「キュー先頭」、末尾を「キュー末尾」と呼びます。要素をキュー末尾に加える操作を「エンキュー」、キュー先頭の要素を削除する操作を「デキュー」と呼びます。

      +

      キューの先入れ先出し規則

      +

      図 5-4   キューの先入れ先出し規則

      -

      5.2.1   キューの一般的な操作

      -

      キューの一般的な操作を下表に示します。メソッド名はプログラミング言語によって異なる場合があることに注意してください。ここでは、スタックで使用したのと同じ命名規則を使用します。

      +

      5.2.1   キューの基本操作

      +

      キューの基本操作を以下の表に示します。なお、メソッド名はプログラミング言語によって異なる場合があります。ここではスタックと同じ命名を採用します。

      表 5-2   キュー操作の効率

      @@ -4454,31 +4454,31 @@ push() -要素をエンキュー、テールに追加 +要素をエンキューし、キュー末尾に追加する \(O(1)\) pop() -ヘッド要素をデキュー +キュー先頭の要素をデキューする \(O(1)\) peek() -ヘッド要素にアクセス +キュー先頭の要素にアクセスする \(O(1)\)
      -

      プログラミング言語で用意されているキュークラスを直接使用できます:

      -
      +

      プログラミング言語に用意された既存のキュークラスをそのまま利用できます:

      +
      queue.py
      from collections import deque
       
       # キューを初期化
      -# Pythonでは、一般的にdequeクラスをキューとして使用します
      -# queue.Queue()は純粋なキュークラスですが、使いにくいため推奨されません
      +# Python では、通常は双方向キュークラス deque をキューとして使用する
      +# queue.Queue() は純粋なキュークラスだが、やや使いにくいため非推奨
       que: deque[int] = deque()
       
       # 要素をエンキュー
      @@ -4488,7 +4488,7 @@
       que.append(5)
       que.append(4)
       
      -# 最初の要素にアクセス
      +# キュー先頭の要素にアクセス
       front: int = que[0]
       
       # 要素をデキュー
      @@ -4497,7 +4497,7 @@
       # キューの長さを取得
       size: int = len(que)
       
      -# キューが空かどうかチェック
      +# キューが空かどうかを判定
       is_empty: bool = len(que) == 0
       
      @@ -4512,7 +4512,7 @@ queue.push(5); queue.push(4); -/* 最初の要素にアクセス */ +/* キュー先頭の要素にアクセス */ int front = queue.front(); /* 要素をデキュー */ @@ -4521,7 +4521,7 @@ /* キューの長さを取得 */ int size = queue.size(); -/* キューが空かどうかチェック */ +/* キューが空かどうかを判定 */ bool empty = queue.empty();
      @@ -4536,7 +4536,7 @@ queue.offer(5); queue.offer(4); -/* 最初の要素にアクセス */ +/* キュー先頭の要素にアクセス */ int peek = queue.peek(); /* 要素をデキュー */ @@ -4545,7 +4545,7 @@ /* キューの長さを取得 */ int size = queue.size(); -/* キューが空かどうかチェック */ +/* キューが空かどうかを判定 */ boolean isEmpty = queue.isEmpty();
      @@ -4560,7 +4560,7 @@ queue.Enqueue(5); queue.Enqueue(4); -/* 最初の要素にアクセス */ +/* キュー先頭の要素にアクセス */ int peek = queue.Peek(); /* 要素をデキュー */ @@ -4569,13 +4569,13 @@ /* キューの長さを取得 */ int size = queue.Count; -/* キューが空かどうかチェック */ +/* キューが空かどうかを判定 */ bool isEmpty = queue.Count == 0;
      queue_test.go
      /* キューを初期化 */
      -// Goでは、listをキューとして使用
      +// Go では、list をキューとして使用する
       queue := list.New()
       
       /* 要素をエンキュー */
      @@ -4585,7 +4585,7 @@
       queue.PushBack(5)
       queue.PushBack(4)
       
      -/* 最初の要素にアクセス */
      +/* キュー先頭の要素にアクセス */
       peek := queue.Front()
       
       /* 要素をデキュー */
      @@ -4595,13 +4595,13 @@
       /* キューの長さを取得 */
       size := queue.Len()
       
      -/* キューが空かどうかチェック */
      +/* キューが空かどうかを判定 */
       isEmpty := queue.Len() == 0
       
      queue.swift
      /* キューを初期化 */
      -// Swiftには組み込みのキュークラスがないため、Arrayをキューとして使用
      +// Swift には組み込みのキュークラスがないため、Array をキューとして使える
       var queue: [Int] = []
       
       /* 要素をエンキュー */
      @@ -4611,23 +4611,23 @@
       queue.append(5)
       queue.append(4)
       
      -/* 最初の要素にアクセス */
      +/* キュー先頭の要素にアクセス */
       let peek = queue.first!
       
       /* 要素をデキュー */
      -// 配列なので、removeFirstの計算量はO(n)
      +// 配列であるため、removeFirst の計算量は O(n)
       let pool = queue.removeFirst()
       
       /* キューの長さを取得 */
       let size = queue.count
       
      -/* キューが空かどうかチェック */
      +/* キューが空かどうかを判定 */
       let isEmpty = queue.isEmpty
       
      queue.js
      /* キューを初期化 */
      -// JavaScriptには組み込みのキューがないため、Arrayをキューとして使用
      +// JavaScript には組み込みのキューがないため、Array をキューとして使える
       const queue = [];
       
       /* 要素をエンキュー */
      @@ -4637,23 +4637,23 @@
       queue.push(5);
       queue.push(4);
       
      -/* 最初の要素にアクセス */
      +/* キュー先頭の要素にアクセス */
       const peek = queue[0];
       
       /* 要素をデキュー */
      -// 基礎構造が配列なので、shift()メソッドの時間計算量はO(n)
      +// 基盤は配列であるため、shift() メソッドの時間計算量は O(n)
       const pop = queue.shift();
       
       /* キューの長さを取得 */
       const size = queue.length;
       
      -/* キューが空かどうかチェック */
      +/* キューが空かどうかを判定 */
       const empty = queue.length === 0;
       
      queue.ts
      /* キューを初期化 */
      -// TypeScriptには組み込みのキューがないため、Arrayをキューとして使用
      +// TypeScript には組み込みのキューがないため、Array をキューとして使える
       const queue: number[] = [];
       
       /* 要素をエンキュー */
      @@ -4663,23 +4663,23 @@
       queue.push(5);
       queue.push(4);
       
      -/* 最初の要素にアクセス */
      +/* キュー先頭の要素にアクセス */
       const peek = queue[0];
       
       /* 要素をデキュー */
      -// 基礎構造が配列なので、shift()メソッドの時間計算量はO(n)
      +// 基盤は配列であるため、shift() メソッドの時間計算量は O(n)
       const pop = queue.shift();
       
       /* キューの長さを取得 */
       const size = queue.length;
       
      -/* キューが空かどうかチェック */
      +/* キューが空かどうかを判定 */
       const empty = queue.length === 0;
       
      queue.dart
      /* キューを初期化 */
      -// DartのQueueクラスは双方向キューですが、キューとして使用できます
      +// Dart では、キュークラス Qeque は双方向キューであり、キューとしても使用できる
       Queue<int> queue = Queue();
       
       /* 要素をエンキュー */
      @@ -4689,7 +4689,7 @@
       queue.add(5);
       queue.add(4);
       
      -/* 最初の要素にアクセス */
      +/* キュー先頭の要素にアクセス */
       int peek = queue.first;
       
       /* 要素をデキュー */
      @@ -4698,13 +4698,13 @@
       /* キューの長さを取得 */
       int size = queue.length;
       
      -/* キューが空かどうかチェック */
      +/* キューが空かどうかを判定 */
       bool isEmpty = queue.isEmpty;
       
      queue.rs
      /* 双方向キューを初期化 */
      -// Rustでは、双方向キューを通常のキューとして使用
      +// Rust では双方向キューを通常のキューとして使う
       let mut deque: VecDeque<u32> = VecDeque::new();
       
       /* 要素をエンキュー */
      @@ -4714,7 +4714,7 @@
       deque.push_back(5);
       deque.push_back(4);
       
      -/* 最初の要素にアクセス */
      +/* キュー先頭の要素にアクセス */
       if let Some(front) = deque.front() {
       }
       
      @@ -4725,28 +4725,78 @@
       /* キューの長さを取得 */
       let size = deque.len();
       
      -/* キューが空かどうかチェック */
      +/* キューが空かどうかを判定 */
       let is_empty = deque.is_empty();
       
      -
      queue.c
      // Cは組み込みのキューを提供していません
      +
      queue.c
      // C には組み込みのキューがない
       
      -
      queue.kt
      
      +
      queue.kt
      /* キューを初期化 */
      +val queue = LinkedList<Int>()
      +
      +/* 要素をエンキュー */
      +queue.offer(1)
      +queue.offer(3)
      +queue.offer(2)
      +queue.offer(5)
      +queue.offer(4)
      +
      +/* キュー先頭の要素にアクセス */
      +val peek = queue.peek()
      +
      +/* 要素をデキュー */
      +val pop = queue.poll()
      +
      +/* キューの長さを取得 */
      +val size = queue.size
      +
      +/* キューが空かどうかを判定 */
      +val isEmpty = queue.isEmpty()
      +
      +
      +
      +
      queue.rb
      # キューを初期化
      +# Ruby 組み込みのキュー(Thread::Queue) には peek と走査メソッドがないため、Array をキューとして使える
      +queue = []
      +
      +# 要素をエンキュー
      +queue.push(1)
      +queue.push(3)
      +queue.push(2)
      +queue.push(5)
      +queue.push(4)
      +
      +# キュー先頭の要素にアクセス
      +peek = queue.first
      +
      +# 要素をデキュー
      +# 注意:配列であるため、Array#shift メソッドの時間計算量は O(n)
      +pop = queue.shift
      +
      +# キューの長さを取得
      +size = queue.length
      +
      +# キューが空かどうかを判定
      +is_empty = queue.empty?
       
      +
      +可視化実行 +

      https://pythontutor.com/render.html#code=from%20collections%20import%20deque%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E9%98%9F%E5%88%97%0A%20%20%20%20%23%20%E5%9C%A8%20Python%20%E4%B8%AD%EF%BC%8C%E6%88%91%E4%BB%AC%E4%B8%80%E8%88%AC%E5%B0%86%E5%8F%8C%E5%90%91%E9%98%9F%E5%88%97%E7%B1%BB%20deque%20%E7%9C%8B%E4%BD%9C%E9%98%9F%E5%88%97%E4%BD%BF%E7%94%A8%0A%20%20%20%20%23%20%E8%99%BD%E7%84%B6%20queue.Queue%28%29%20%E6%98%AF%E7%BA%AF%E6%AD%A3%E7%9A%84%E9%98%9F%E5%88%97%E7%B1%BB%EF%BC%8C%E4%BD%86%E4%B8%8D%E5%A4%AA%E5%A5%BD%E7%94%A8%0A%20%20%20%20que%20%3D%20deque%28%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E9%98%9F%0A%20%20%20%20que.append%281%29%0A%20%20%20%20que.append%283%29%0A%20%20%20%20que.append%282%29%0A%20%20%20%20que.append%285%29%0A%20%20%20%20que.append%284%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%0A%20%20%20%20front%20%3D%20que%5B0%5D%0A%20%20%20%20print%28%22%E9%98%9F%E9%A6%96%E5%85%83%E7%B4%A0%20front%20%3D%22,%20front%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E9%98%9F%0A%20%20%20%20pop%20%3D%20que.popleft%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E9%98%9F%E5%90%8E%20que%20%3D%22,%20que%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E9%98%9F%E5%88%97%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28que%29%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28que%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E9%98%9F%E5%88%97%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=3&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false

      +

      5.2.2   キューの実装

      -

      キューを実装するには、一方の端で要素を追加し、もう一方の端で要素を削除できるデータ構造が必要です。連結リストと配列の両方がこの要件を満たします。

      -

      1.   連結リストベースの実装

      -

      下図に示すように、連結リストの「ヘッドノード」と「テールノード」をそれぞれキューの「フロント」と「リア」と考えることができます。ノードは後ろでのみ追加でき、前でのみ削除できるように規定されています。

      +

      キューを実装するには、一方の端で要素を追加し、もう一方の端で要素を削除できるデータ構造が必要です。連結リストと配列はいずれもこの条件を満たします。

      +

      1.   連結リストに基づく実装

      +

      下図のように、連結リストの「先頭ノード」と「末尾ノード」をそれぞれ「キュー先頭」と「キュー末尾」とみなし、キュー末尾ではノードの追加のみ、キュー先頭ではノードの削除のみを行うようにします。

      -

      連結リストによるキュー実装のエンキューとデキュー操作

      +

      連結リストでキューを実装したエンキューとデキュー操作

      linkedlist_queue_push

      @@ -4756,266 +4806,924 @@
      -

      図 5-5   連結リストによるキュー実装のエンキューとデキュー操作

      +

      図 5-5   連結リストでキューを実装したエンキューとデキュー操作

      -

      以下は、連結リストを使用してキューを実装するコードです:

      +

      以下は連結リストでキューを実装するコードです:

      -
      linkedlist_queue.py
      class LinkedListQueue:
      -    """連結リストベースのキュークラス"""
      -
      -    def __init__(self):
      -        """コンストラクタ"""
      -        self._front: ListNode | None = None  # ヘッドノード front
      -        self._rear: ListNode | None = None  # テールノード rear
      -        self._size: int = 0
      -
      -    def size(self) -> int:
      -        """キューの長さを取得"""
      -        return self._size
      -
      -    def is_empty(self) -> bool:
      -        """キューが空かどうかを判定"""
      -        return self._size == 0
      -
      -    def push(self, num: int):
      -        """エンキュー"""
      -        # テールノードの後ろに num を追加
      -        node = ListNode(num)
      -        # キューが空の場合、ヘッドとテールノードの両方をそのノードに向ける
      -        if self._front is None:
      -            self._front = node
      -            self._rear = node
      -        # キューが空でない場合、そのノードをテールノードの後ろに追加
      -        else:
      -            self._rear.next = node
      -            self._rear = node
      -        self._size += 1
      -
      -    def pop(self) -> int:
      -        """デキュー"""
      -        num = self.peek()
      -        # ヘッドノードを削除
      -        self._front = self._front.next
      -        self._size -= 1
      -        return num
      -
      -    def peek(self) -> int:
      -        """フロント要素にアクセス"""
      -        if self.is_empty():
      -            raise IndexError("Queue is empty")
      -        return self._front.val
      -
      -    def to_list(self) -> list[int]:
      -        """出力用のリストに変換"""
      -        queue = []
      -        temp = self._front
      -        while temp:
      -            queue.append(temp.val)
      -            temp = temp.next
      -        return queue
      -
      -
      -
      -
      linkedlist_queue.cpp
      /* 連結リストに基づくキュークラス */
      -class LinkedListQueue {
      -  private:
      -    ListNode *front, *rear; // 先頭ノードfront、末尾ノードrear
      -    int queSize;
      -
      -  public:
      -    LinkedListQueue() {
      -        front = nullptr;
      -        rear = nullptr;
      -        queSize = 0;
      -    }
      +
      linkedlist_queue.py
      class LinkedListQueue:
      +    """連結リストベースのキュー"""
      +
      +    def __init__(self):
      +        """コンストラクタ"""
      +        self._front: ListNode | None = None  # 先頭ノード front
      +        self._rear: ListNode | None = None  # 末尾ノード rear
      +        self._size: int = 0
      +
      +    def size(self) -> int:
      +        """キューの長さを取得"""
      +        return self._size
       
      -    ~LinkedListQueue() {
      -        // 連結リストを走査、ノードを削除、メモリを解放
      -        freeMemoryLinkedList(front);
      -    }
      -
      -    /* キューの長さを取得 */
      -    int size() {
      -        return queSize;
      -    }
      -
      -    /* キューが空かどうかを判定 */
      -    bool isEmpty() {
      -        return queSize == 0;
      -    }
      -
      -    /* エンキュー */
      -    void push(int num) {
      -        // 末尾ノードの後ろにnumを追加
      -        ListNode *node = new ListNode(num);
      -        // キューが空の場合、先頭と末尾ノードの両方をそのノードに向ける
      -        if (front == nullptr) {
      -            front = node;
      -            rear = node;
      -        }
      -        // キューが空でない場合、そのノードを末尾ノードの後ろに追加
      -        else {
      -            rear->next = node;
      -            rear = node;
      -        }
      -        queSize++;
      -    }
      +    def is_empty(self) -> bool:
      +        """キューが空かどうかを判定"""
      +        return self._size == 0
      +
      +    def push(self, num: int):
      +        """エンキュー"""
      +        # 末尾ノードの後ろに num を追加
      +        node = ListNode(num)
      +        # キューが空なら、先頭・末尾ノードをともにそのノードに設定
      +        if self._front is None:
      +            self._front = node
      +            self._rear = node
      +        # キューが空でなければ、そのノードを末尾ノードの後ろに追加
      +        else:
      +            self._rear.next = node
      +            self._rear = node
      +        self._size += 1
      +
      +    def pop(self) -> int:
      +        """デキュー"""
      +        num = self.peek()
      +        # 先頭ノードを削除
      +        self._front = self._front.next
      +        self._size -= 1
      +        return num
      +
      +    def peek(self) -> int:
      +        """キュー先頭の要素にアクセス"""
      +        if self.is_empty():
      +            raise IndexError("キューが空です")
      +        return self._front.val
       
      -    /* デキュー */
      -    int pop() {
      -        int num = peek();
      -        // 先頭ノードを削除
      -        ListNode *tmp = front;
      -        front = front->next;
      -        // メモリを解放
      -        delete tmp;
      -        queSize--;
      -        return num;
      -    }
      -
      -    /* 先頭要素にアクセス */
      -    int peek() {
      -        if (size() == 0)
      -            throw out_of_range("Queue is empty");
      -        return front->val;
      -    }
      -
      -    /* 連結リストをVectorに変換して返却 */
      -    vector<int> toVector() {
      -        ListNode *node = front;
      -        vector<int> res(size());
      -        for (int i = 0; i < res.size(); i++) {
      -            res[i] = node->val;
      -            node = node->next;
      -        }
      -        return res;
      -    }
      -};
      +    def to_list(self) -> list[int]:
      +        """表示用にリストへ変換"""
      +        queue = []
      +        temp = self._front
      +        while temp:
      +            queue.append(temp.val)
      +            temp = temp.next
      +        return queue
       
      -
      linkedlist_queue.java
      /* 連結リストに基づくキュークラス */
      -class LinkedListQueue {
      -    private ListNode front, rear; // 先頭ノード front、末尾ノード rear
      -    private int queSize = 0;
      -
      -    public LinkedListQueue() {
      -        front = null;
      -        rear = null;
      -    }
      -
      -    /* キューの長さを取得 */
      -    public int size() {
      -        return queSize;
      -    }
      -
      -    /* キューが空かどうかを判定 */
      -    public boolean isEmpty() {
      -        return size() == 0;
      -    }
      -
      -    /* エンキュー */
      -    public void push(int num) {
      -        // 末尾ノードの後ろに num を追加
      -        ListNode node = new ListNode(num);
      -        // キューが空の場合、先頭と末尾ノードの両方をそのノードにポイント
      -        if (front == null) {
      -            front = node;
      -            rear = node;
      -        // キューが空でない場合、そのノードを末尾ノードの後ろに追加
      -        } else {
      -            rear.next = node;
      -            rear = node;
      -        }
      -        queSize++;
      -    }
      -
      -    /* デキュー */
      -    public int pop() {
      -        int num = peek();
      -        // 先頭ノードを削除
      -        front = front.next;
      -        queSize--;
      -        return num;
      +
      linkedlist_queue.cpp
      /* 連結リストベースのキュー */
      +class LinkedListQueue {
      +  private:
      +    ListNode *front, *rear; // 先頭ノード front、末尾ノード rear
      +    int queSize;
      +
      +  public:
      +    LinkedListQueue() {
      +        front = nullptr;
      +        rear = nullptr;
      +        queSize = 0;
      +    }
      +
      +    ~LinkedListQueue() {
      +        // 連結リストを走査してノードを削除し、メモリを解放する
      +        freeMemoryLinkedList(front);
      +    }
      +
      +    /* キューの長さを取得 */
      +    int size() {
      +        return queSize;
      +    }
      +
      +    /* キューが空かどうかを判定 */
      +    bool isEmpty() {
      +        return queSize == 0;
      +    }
      +
      +    /* エンキュー */
      +    void push(int num) {
      +        // 末尾ノードの後ろに num を追加
      +        ListNode *node = new ListNode(num);
      +        // キューが空なら、先頭・末尾ノードをともにそのノードに設定
      +        if (front == nullptr) {
      +            front = node;
      +            rear = node;
      +        }
      +        // キューが空でなければ、そのノードを末尾ノードの後ろに追加
      +        else {
      +            rear->next = node;
      +            rear = node;
      +        }
      +        queSize++;
           }
       
      -    /* 先頭要素にアクセス */
      -    public int peek() {
      -        if (isEmpty())
      -            throw new IndexOutOfBoundsException();
      -        return front.val;
      -    }
      -
      -    /* 連結リストを配列に変換して返す */
      -    public int[] toArray() {
      -        ListNode node = front;
      -        int[] res = new int[size()];
      -        for (int i = 0; i < res.length; i++) {
      -            res[i] = node.val;
      -            node = node.next;
      -        }
      -        return res;
      -    }
      -}
      +    /* デキュー */
      +    int pop() {
      +        int num = peek();
      +        // 先頭ノードを削除
      +        ListNode *tmp = front;
      +        front = front->next;
      +        // メモリを解放する
      +        delete tmp;
      +        queSize--;
      +        return num;
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    int peek() {
      +        if (size() == 0)
      +            throw out_of_range("キューが空です");
      +        return front->val;
      +    }
      +
      +    /* 連結リストを Vector に変換して返す */
      +    vector<int> toVector() {
      +        ListNode *node = front;
      +        vector<int> res(size());
      +        for (int i = 0; i < res.size(); i++) {
      +            res[i] = node->val;
      +            node = node->next;
      +        }
      +        return res;
      +    }
      +};
       
      -
      linkedlist_queue.cs
      [class]{LinkedListQueue}-[func]{}
      +
      linkedlist_queue.java
      /* 連結リストベースのキュー */
      +class LinkedListQueue {
      +    private ListNode front, rear; // 先頭ノード front、末尾ノード rear
      +    private int queSize = 0;
      +
      +    public LinkedListQueue() {
      +        front = null;
      +        rear = null;
      +    }
      +
      +    /* キューの長さを取得 */
      +    public int size() {
      +        return queSize;
      +    }
      +
      +    /* キューが空かどうかを判定 */
      +    public boolean isEmpty() {
      +        return size() == 0;
      +    }
      +
      +    /* エンキュー */
      +    public void push(int num) {
      +        // 末尾ノードの後ろに num を追加
      +        ListNode node = new ListNode(num);
      +        // キューが空なら、先頭・末尾ノードをともにそのノードに設定
      +        if (front == null) {
      +            front = node;
      +            rear = node;
      +        // キューが空でなければ、そのノードを末尾ノードの後ろに追加
      +        } else {
      +            rear.next = node;
      +            rear = node;
      +        }
      +        queSize++;
      +    }
      +
      +    /* デキュー */
      +    public int pop() {
      +        int num = peek();
      +        // 先頭ノードを削除
      +        front = front.next;
      +        queSize--;
      +        return num;
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    public int peek() {
      +        if (isEmpty())
      +            throw new IndexOutOfBoundsException();
      +        return front.val;
      +    }
      +
      +    /* 連結リストを Array に変換して返す */
      +    public int[] toArray() {
      +        ListNode node = front;
      +        int[] res = new int[size()];
      +        for (int i = 0; i < res.length; i++) {
      +            res[i] = node.val;
      +            node = node.next;
      +        }
      +        return res;
      +    }
      +}
       
      -
      linkedlist_queue.go
      [class]{linkedListQueue}-[func]{}
      +
      linkedlist_queue.cs
      /* 連結リストベースのキュー */
      +class LinkedListQueue {
      +    ListNode? front, rear;  // 先頭ノード front、末尾ノード rear
      +    int queSize = 0;
      +
      +    public LinkedListQueue() {
      +        front = null;
      +        rear = null;
      +    }
      +
      +    /* キューの長さを取得 */
      +    public int Size() {
      +        return queSize;
      +    }
      +
      +    /* キューが空かどうかを判定 */
      +    public bool IsEmpty() {
      +        return Size() == 0;
      +    }
      +
      +    /* エンキュー */
      +    public void Push(int num) {
      +        // 末尾ノードの後ろに num を追加
      +        ListNode node = new(num);
      +        // キューが空なら、先頭・末尾ノードをともにそのノードに設定
      +        if (front == null) {
      +            front = node;
      +            rear = node;
      +            // キューが空でなければ、そのノードを末尾ノードの後ろに追加
      +        } else if (rear != null) {
      +            rear.next = node;
      +            rear = node;
      +        }
      +        queSize++;
      +    }
      +
      +    /* デキュー */
      +    public int Pop() {
      +        int num = Peek();
      +        // 先頭ノードを削除
      +        front = front?.next;
      +        queSize--;
      +        return num;
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    public int Peek() {
      +        if (IsEmpty())
      +            throw new Exception();
      +        return front!.val;
      +    }
      +
      +    /* 連結リストを Array に変換して返す */
      +    public int[] ToArray() {
      +        if (front == null)
      +            return [];
      +
      +        ListNode? node = front;
      +        int[] res = new int[Size()];
      +        for (int i = 0; i < res.Length; i++) {
      +            res[i] = node!.val;
      +            node = node.next;
      +        }
      +        return res;
      +    }
      +}
       
      -
      linkedlist_queue.swift
      [class]{LinkedListQueue}-[func]{}
      +
      linkedlist_queue.go
      /* 連結リストベースのキュー */
      +type linkedListQueue struct {
      +    // 組み込みパッケージ list でキューを実装する
      +    data *list.List
      +}
      +
      +/* キューを初期化 */
      +func newLinkedListQueue() *linkedListQueue {
      +    return &linkedListQueue{
      +        data: list.New(),
      +    }
      +}
      +
      +/* エンキュー */
      +func (s *linkedListQueue) push(value any) {
      +    s.data.PushBack(value)
      +}
      +
      +/* デキュー */
      +func (s *linkedListQueue) pop() any {
      +    if s.isEmpty() {
      +        return nil
      +    }
      +    e := s.data.Front()
      +    s.data.Remove(e)
      +    return e.Value
      +}
      +
      +/* キュー先頭の要素にアクセス */
      +func (s *linkedListQueue) peek() any {
      +    if s.isEmpty() {
      +        return nil
      +    }
      +    e := s.data.Front()
      +    return e.Value
      +}
      +
      +/* キューの長さを取得 */
      +func (s *linkedListQueue) size() int {
      +    return s.data.Len()
      +}
      +
      +/* キューが空かどうかを判定 */
      +func (s *linkedListQueue) isEmpty() bool {
      +    return s.data.Len() == 0
      +}
      +
      +/* 表示用に List を取得 */
      +func (s *linkedListQueue) toList() *list.List {
      +    return s.data
      +}
       
      -
      linkedlist_queue.js
      [class]{LinkedListQueue}-[func]{}
      +
      linkedlist_queue.swift
      /* 連結リストベースのキュー */
      +class LinkedListQueue {
      +    private var front: ListNode? // 先頭ノード
      +    private var rear: ListNode? // 末尾ノード
      +    private var _size: Int
      +
      +    init() {
      +        _size = 0
      +    }
      +
      +    /* キューの長さを取得 */
      +    func size() -> Int {
      +        _size
      +    }
      +
      +    /* キューが空かどうかを判定 */
      +    func isEmpty() -> Bool {
      +        size() == 0
      +    }
      +
      +    /* エンキュー */
      +    func push(num: Int) {
      +        // 末尾ノードの後ろに num を追加
      +        let node = ListNode(x: num)
      +        // キューが空なら、先頭・末尾ノードをともにそのノードに設定
      +        if front == nil {
      +            front = node
      +            rear = node
      +        }
      +        // キューが空でなければ、そのノードを末尾ノードの後ろに追加
      +        else {
      +            rear?.next = node
      +            rear = node
      +        }
      +        _size += 1
      +    }
      +
      +    /* デキュー */
      +    @discardableResult
      +    func pop() -> Int {
      +        let num = peek()
      +        // 先頭ノードを削除
      +        front = front?.next
      +        _size -= 1
      +        return num
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    func peek() -> Int {
      +        if isEmpty() {
      +            fatalError("キューが空です")
      +        }
      +        return front!.val
      +    }
      +
      +    /* 連結リストを Array に変換して返す */
      +    func toArray() -> [Int] {
      +        var node = front
      +        var res = Array(repeating: 0, count: size())
      +        for i in res.indices {
      +            res[i] = node!.val
      +            node = node?.next
      +        }
      +        return res
      +    }
      +}
       
      -
      linkedlist_queue.ts
      [class]{LinkedListQueue}-[func]{}
      +
      linkedlist_queue.js
      /* 連結リストベースのキュー */
      +class LinkedListQueue {
      +    #front; // 先頭ノード #front
      +    #rear; // 末尾ノード #rear
      +    #queSize = 0;
      +
      +    constructor() {
      +        this.#front = null;
      +        this.#rear = null;
      +    }
      +
      +    /* キューの長さを取得 */
      +    get size() {
      +        return this.#queSize;
      +    }
      +
      +    /* キューが空かどうかを判定 */
      +    isEmpty() {
      +        return this.size === 0;
      +    }
      +
      +    /* エンキュー */
      +    push(num) {
      +        // 末尾ノードの後ろに num を追加
      +        const node = new ListNode(num);
      +        // キューが空なら、先頭・末尾ノードをともにそのノードに設定
      +        if (!this.#front) {
      +            this.#front = node;
      +            this.#rear = node;
      +            // キューが空でなければ、そのノードを末尾ノードの後ろに追加
      +        } else {
      +            this.#rear.next = node;
      +            this.#rear = node;
      +        }
      +        this.#queSize++;
      +    }
      +
      +    /* デキュー */
      +    pop() {
      +        const num = this.peek();
      +        // 先頭ノードを削除
      +        this.#front = this.#front.next;
      +        this.#queSize--;
      +        return num;
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    peek() {
      +        if (this.size === 0) throw new Error('キューが空');
      +        return this.#front.val;
      +    }
      +
      +    /* 連結リストを Array に変換して返す */
      +    toArray() {
      +        let node = this.#front;
      +        const res = new Array(this.size);
      +        for (let i = 0; i < res.length; i++) {
      +            res[i] = node.val;
      +            node = node.next;
      +        }
      +        return res;
      +    }
      +}
       
      -
      linkedlist_queue.dart
      [class]{LinkedListQueue}-[func]{}
      +
      linkedlist_queue.ts
      /* 連結リストベースのキュー */
      +class LinkedListQueue {
      +    private front: ListNode | null; // 先頭ノード front
      +    private rear: ListNode | null; // 末尾ノード rear
      +    private queSize: number = 0;
      +
      +    constructor() {
      +        this.front = null;
      +        this.rear = null;
      +    }
      +
      +    /* キューの長さを取得 */
      +    get size(): number {
      +        return this.queSize;
      +    }
      +
      +    /* キューが空かどうかを判定 */
      +    isEmpty(): boolean {
      +        return this.size === 0;
      +    }
      +
      +    /* エンキュー */
      +    push(num: number): void {
      +        // 末尾ノードの後ろに num を追加
      +        const node = new ListNode(num);
      +        // キューが空なら、先頭・末尾ノードをともにそのノードに設定
      +        if (!this.front) {
      +            this.front = node;
      +            this.rear = node;
      +            // キューが空でなければ、そのノードを末尾ノードの後ろに追加
      +        } else {
      +            this.rear!.next = node;
      +            this.rear = node;
      +        }
      +        this.queSize++;
      +    }
      +
      +    /* デキュー */
      +    pop(): number {
      +        const num = this.peek();
      +        if (!this.front) throw new Error('キューが空です');
      +        // 先頭ノードを削除
      +        this.front = this.front.next;
      +        this.queSize--;
      +        return num;
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    peek(): number {
      +        if (this.size === 0) throw new Error('キューが空です');
      +        return this.front!.val;
      +    }
      +
      +    /* 連結リストを Array に変換して返す */
      +    toArray(): number[] {
      +        let node = this.front;
      +        const res = new Array<number>(this.size);
      +        for (let i = 0; i < res.length; i++) {
      +            res[i] = node!.val;
      +            node = node!.next;
      +        }
      +        return res;
      +    }
      +}
       
      -
      linkedlist_queue.rs
      [class]{LinkedListQueue}-[func]{}
      +
      linkedlist_queue.dart
      /* 連結リストベースのキュー */
      +class LinkedListQueue {
      +  ListNode? _front; // 先頭ノード _front
      +  ListNode? _rear; // 末尾ノード _rear
      +  int _queSize = 0; // キューの長さ
      +
      +  LinkedListQueue() {
      +    _front = null;
      +    _rear = null;
      +  }
      +
      +  /* キューの長さを取得 */
      +  int size() {
      +    return _queSize;
      +  }
      +
      +  /* キューが空かどうかを判定 */
      +  bool isEmpty() {
      +    return _queSize == 0;
      +  }
      +
      +  /* エンキュー */
      +  void push(int _num) {
      +    // 末尾ノードの後ろに _num を追加
      +    final node = ListNode(_num);
      +    // キューが空なら、先頭・末尾ノードをともにそのノードに設定
      +    if (_front == null) {
      +      _front = node;
      +      _rear = node;
      +    } else {
      +      // キューが空でなければ、そのノードを末尾ノードの後ろに追加
      +      _rear!.next = node;
      +      _rear = node;
      +    }
      +    _queSize++;
      +  }
      +
      +  /* デキュー */
      +  int pop() {
      +    final int _num = peek();
      +    // 先頭ノードを削除
      +    _front = _front!.next;
      +    _queSize--;
      +    return _num;
      +  }
      +
      +  /* キュー先頭の要素にアクセス */
      +  int peek() {
      +    if (_queSize == 0) {
      +      throw Exception('キューが空です');
      +    }
      +    return _front!.val;
      +  }
      +
      +  /* 連結リストを Array に変換して返す */
      +  List<int> toArray() {
      +    ListNode? node = _front;
      +    final List<int> queue = [];
      +    while (node != null) {
      +      queue.add(node.val);
      +      node = node.next;
      +    }
      +    return queue;
      +  }
      +}
       
      -
      linkedlist_queue.c
      [class]{LinkedListQueue}-[func]{}
      +
      linkedlist_queue.rs
      /* 連結リストベースのキュー */
      +#[allow(dead_code)]
      +pub struct LinkedListQueue<T> {
      +    front: Option<Rc<RefCell<ListNode<T>>>>, // 先頭ノード front
      +    rear: Option<Rc<RefCell<ListNode<T>>>>,  // 末尾ノード rear
      +    que_size: usize,                         // キューの長さ
      +}
      +
      +impl<T: Copy> LinkedListQueue<T> {
      +    pub fn new() -> Self {
      +        Self {
      +            front: None,
      +            rear: None,
      +            que_size: 0,
      +        }
      +    }
      +
      +    /* キューの長さを取得 */
      +    pub fn size(&self) -> usize {
      +        return self.que_size;
      +    }
      +
      +    /* キューが空かどうかを判定 */
      +    pub fn is_empty(&self) -> bool {
      +        return self.que_size == 0;
      +    }
      +
      +    /* エンキュー */
      +    pub fn push(&mut self, num: T) {
      +        // 末尾ノードの後ろに num を追加
      +        let new_rear = ListNode::new(num);
      +        match self.rear.take() {
      +            // キューが空でなければ、そのノードを末尾ノードの後ろに追加
      +            Some(old_rear) => {
      +                old_rear.borrow_mut().next = Some(new_rear.clone());
      +                self.rear = Some(new_rear);
      +            }
      +            // キューが空なら、先頭・末尾ノードをともにそのノードに設定
      +            None => {
      +                self.front = Some(new_rear.clone());
      +                self.rear = Some(new_rear);
      +            }
      +        }
      +        self.que_size += 1;
      +    }
      +
      +    /* デキュー */
      +    pub fn pop(&mut self) -> Option<T> {
      +        self.front.take().map(|old_front| {
      +            match old_front.borrow_mut().next.take() {
      +                Some(new_front) => {
      +                    self.front = Some(new_front);
      +                }
      +                None => {
      +                    self.rear.take();
      +                }
      +            }
      +            self.que_size -= 1;
      +            old_front.borrow().val
      +        })
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    pub fn peek(&self) -> Option<&Rc<RefCell<ListNode<T>>>> {
      +        self.front.as_ref()
      +    }
      +
      +    /* 連結リストを Array に変換して返す */
      +    pub fn to_array(&self, head: Option<&Rc<RefCell<ListNode<T>>>>) -> Vec<T> {
      +        let mut res: Vec<T> = Vec::new();
      +
      +        fn recur<T: Copy>(cur: Option<&Rc<RefCell<ListNode<T>>>>, res: &mut Vec<T>) {
      +            if let Some(cur) = cur {
      +                res.push(cur.borrow().val);
      +                recur(cur.borrow().next.as_ref(), res);
      +            }
      +        }
      +
      +        recur(head, &mut res);
      +
      +        res
      +    }
      +}
       
      -
      linkedlist_queue.kt
      [class]{LinkedListQueue}-[func]{}
      +
      linkedlist_queue.c
      /* 連結リストベースのキュー */
      +typedef struct {
      +    ListNode *front, *rear;
      +    int queSize;
      +} LinkedListQueue;
      +
      +/* コンストラクタ */
      +LinkedListQueue *newLinkedListQueue() {
      +    LinkedListQueue *queue = (LinkedListQueue *)malloc(sizeof(LinkedListQueue));
      +    queue->front = NULL;
      +    queue->rear = NULL;
      +    queue->queSize = 0;
      +    return queue;
      +}
      +
      +/* デストラクタ */
      +void delLinkedListQueue(LinkedListQueue *queue) {
      +    // すべてのノードを解放
      +    while (queue->front != NULL) {
      +        ListNode *tmp = queue->front;
      +        queue->front = queue->front->next;
      +        free(tmp);
      +    }
      +    // queue 構造体を解放する
      +    free(queue);
      +}
      +
      +/* キューの長さを取得 */
      +int size(LinkedListQueue *queue) {
      +    return queue->queSize;
      +}
      +
      +/* キューが空かどうかを判定 */
      +bool empty(LinkedListQueue *queue) {
      +    return (size(queue) == 0);
      +}
      +
      +/* エンキュー */
      +void push(LinkedListQueue *queue, int num) {
      +    // 末尾ノードに node を追加
      +    ListNode *node = newListNode(num);
      +    // キューが空なら、先頭・末尾ノードをともにそのノードに設定
      +    if (queue->front == NULL) {
      +        queue->front = node;
      +        queue->rear = node;
      +    }
      +    // キューが空でなければ、そのノードを末尾ノードの後ろに追加
      +    else {
      +        queue->rear->next = node;
      +        queue->rear = node;
      +    }
      +    queue->queSize++;
      +}
      +
      +/* キュー先頭の要素にアクセス */
      +int peek(LinkedListQueue *queue) {
      +    assert(size(queue) && queue->front);
      +    return queue->front->val;
      +}
      +
      +/* デキュー */
      +int pop(LinkedListQueue *queue) {
      +    int num = peek(queue);
      +    ListNode *tmp = queue->front;
      +    queue->front = queue->front->next;
      +    free(tmp);
      +    queue->queSize--;
      +    return num;
      +}
      +
      +/* キューを出力する */
      +void printLinkedListQueue(LinkedListQueue *queue) {
      +    int *arr = malloc(sizeof(int) * queue->queSize);
      +    // 連結リスト内のデータを配列にコピー
      +    int i;
      +    ListNode *node;
      +    for (i = 0, node = queue->front; i < queue->queSize; i++) {
      +        arr[i] = node->val;
      +        node = node->next;
      +    }
      +    printArray(arr, queue->queSize);
      +    free(arr);
      +}
       
      -
      linkedlist_queue.rb
      [class]{LinkedListQueue}-[func]{}
      +
      linkedlist_queue.kt
      /* 連結リストベースのキュー */
      +class LinkedListQueue(
      +    // 先頭ノード front、末尾ノード rear
      +    private var front: ListNode? = null,
      +    private var rear: ListNode? = null,
      +    private var queSize: Int = 0
      +) {
      +
      +    /* キューの長さを取得 */
      +    fun size(): Int {
      +        return queSize
      +    }
      +
      +    /* キューが空かどうかを判定 */
      +    fun isEmpty(): Boolean {
      +        return size() == 0
      +    }
      +
      +    /* エンキュー */
      +    fun push(num: Int) {
      +        // 末尾ノードの後ろに num を追加
      +        val node = ListNode(num)
      +        // キューが空なら、先頭・末尾ノードをともにそのノードに設定
      +        if (front == null) {
      +            front = node
      +            rear = node
      +            // キューが空でなければ、そのノードを末尾ノードの後ろに追加
      +        } else {
      +            rear?.next = node
      +            rear = node
      +        }
      +        queSize++
      +    }
      +
      +    /* デキュー */
      +    fun pop(): Int {
      +        val num = peek()
      +        // 先頭ノードを削除
      +        front = front?.next
      +        queSize--
      +        return num
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    fun peek(): Int {
      +        if (isEmpty()) throw IndexOutOfBoundsException()
      +        return front!!._val
      +    }
      +
      +    /* 連結リストを Array に変換して返す */
      +    fun toArray(): IntArray {
      +        var node = front
      +        val res = IntArray(size())
      +        for (i in res.indices) {
      +            res[i] = node!!._val
      +            node = node.next
      +        }
      +        return res
      +    }
      +}
      +
      +
      +
      +
      linkedlist_queue.rb
      ### 連結リストで実装したキュー ###
      +class LinkedListQueue
      +  ### キューの長さを取得 ###
      +  attr_reader :size
      +
      +  ### コンストラクタ ###
      +  def initialize
      +    @front = nil  # 先頭ノード front
      +    @rear = nil   # 末尾ノード rear
      +    @size = 0
      +  end
      +
      +  ### キューが空か判定 ###
      +  def is_empty?
      +    @front.nil?
      +  end
      +
      +  ### エンキュー ###
      +  def push(num)
      +    # 末尾ノードの後ろに num を追加
      +    node = ListNode.new(num)
      +
      +    # キューが空なら、先頭ノードと末尾ノードの両方をそのノードに向ける
      +    if @front.nil?
      +      @front = node
      +      @rear = node
      +    # キューが空でなければ、そのノードを末尾ノードの後ろに追加する
      +    else
      +      @rear.next = node
      +      @rear = node
      +    end
      +
      +    @size += 1
      +  end
      +
      +  ### デキュー ###
      +  def pop
      +    num = peek
      +    # 先頭ノードを削除
      +    @front = @front.next
      +    @size -= 1
      +    num
      +  end
      +
      +  ### 先頭要素にアクセス ###
      +  def peek
      +    raise IndexError, 'キューは空です' if is_empty?
      +
      +    @front.val
      +  end
      +
      +  ### 連結リストを Array に変換して返す ###
      +  def to_array
      +    queue = []
      +    temp = @front
      +    while temp
      +      queue << temp.val
      +      temp = temp.next
      +    end
      +    queue
      +  end
      +end
       
      -

      2.   配列ベースの実装

      -

      配列の最初の要素を削除する時間計算量は\(O(n)\)で、デキュー操作が非効率になります。しかし、この問題は以下のように巧妙に回避できます。

      -

      変数frontを使用してフロント要素のインデックスを示し、変数sizeを維持してキューの長さを記録します。rear = front + sizeを定義し、これはテール要素の直後の位置を指します。

      -

      この設計により、配列内の要素の有効な間隔は[front, rear - 1]です。各操作の実装方法を下図に示します。

      +
      +コードの可視化 +

      +

      +
      +

      2.   配列に基づく実装

      +

      配列で先頭要素を削除する時間計算量は \(O(n)\) であり、そのままではデキュー操作の効率が低くなります。しかし、次の巧妙な方法によってこの問題を回避できます。

      +

      変数 front を用いてキュー先頭要素のインデックスを指し、さらに変数 size でキューの長さを記録できます。rear = front + size と定義すると、この式で得られる rear はキュー末尾要素の次の位置を指します。

      +

      この設計に基づくと、配列内で要素を含む有効区間は [front, rear - 1] となります。各種操作の実装方法を下図に示します。

        -
      • エンキュー操作:入力要素をrearインデックスに割り当て、sizeを1増加させます。
      • -
      • デキュー操作:単にfrontを1増加させ、sizeを1減少させます。
      • +
      • エンキュー操作:入力要素を rear の位置に代入し、size を 1 増やします。
      • +
      • デキュー操作:front を 1 増やし、size を 1 減らすだけです。
      -

      エンキューとデキュー操作は両方とも単一の操作のみを必要とし、それぞれの時間計算量は\(O(1)\)です。

      +

      このように、エンキューとデキューはいずれも 1 回の操作だけで済み、時間計算量はともに \(O(1)\) です。

      -

      配列によるキュー実装のエンキューとデキュー操作

      +

      配列でキューを実装したエンキューとデキュー操作

      array_queue_push

      @@ -5025,264 +5733,941 @@
      -

      図 5-6   配列によるキュー実装のエンキューとデキュー操作

      +

      図 5-6   配列でキューを実装したエンキューとデキュー操作

      -

      問題に気づくかもしれません:エンキューとデキュー操作が継続的に実行されると、frontrearの両方が右に移動し、最終的に配列の末尾に到達してそれ以上移動できなくなります。これを解決するために、配列を「循環配列」として扱い、配列の末尾を先頭に接続します。

      -

      循環配列では、frontまたはrearが末尾に到達すると、配列の先頭にループバックする必要があります。この循環パターンは、以下のコードに示すように「剰余演算」で実現できます:

      +

      ここで 1 つ問題があります。エンキューとデキューを繰り返すと、frontrear はどちらも右へ移動し続け、配列の末尾に達するとそれ以上進めなくなります。この問題を解決するために、配列を先頭と末尾がつながった「環状配列」とみなします。

      +

      環状配列では、front または rear が配列末尾を越えたときに、直ちに配列先頭へ戻って走査を続けられるようにする必要があります。この周期的な規則は「剰余演算」によって実現できます。コードは次のとおりです:

      -
      array_queue.py
      class ArrayQueue:
      -    """循環配列ベースのキュークラス"""
      -
      -    def __init__(self, size: int):
      -        """コンストラクタ"""
      -        self._nums: list[int] = [0] * size  # キュー要素を格納する配列
      -        self._front: int = 0  # フロントポインタ、フロント要素を指す
      -        self._size: int = 0  # キューの長さ
      -
      -    def capacity(self) -> int:
      -        """キューの容量を取得"""
      -        return len(self._nums)
      -
      -    def size(self) -> int:
      -        """キューの長さを取得"""
      -        return self._size
      -
      -    def is_empty(self) -> bool:
      -        """キューが空かどうかを判定"""
      -        return self._size == 0
      -
      -    def push(self, num: int):
      -        """エンキュー"""
      -        if self._size == self.capacity():
      -            raise IndexError("Queue is full")
      -        # リアポインタを計算、リアインデックス + 1 を指す
      -        # モジュロ演算を使用してリアポインタを配列の末尾から先頭に戻す
      -        rear: int = (self._front + self._size) % self.capacity()
      -        # num をリアに追加
      -        self._nums[rear] = num
      -        self._size += 1
      -
      -    def pop(self) -> int:
      -        """デキュー"""
      -        num: int = self.peek()
      -        # フロントポインタを1つ後ろに移動、末尾を超えた場合は配列の先頭に戻る
      -        self._front = (self._front + 1) % self.capacity()
      -        self._size -= 1
      -        return num
      -
      -    def peek(self) -> int:
      -        """フロント要素にアクセス"""
      -        if self.is_empty():
      -            raise IndexError("Queue is empty")
      -        return self._nums[self._front]
      -
      -    def to_list(self) -> list[int]:
      -        """出力用の配列を返す"""
      -        res = [0] * self.size()
      -        j: int = self._front
      -        for i in range(self.size()):
      -            res[i] = self._nums[(j % self.capacity())]
      -            j += 1
      -        return res
      +
      array_queue.py
      class ArrayQueue:
      +    """循環配列ベースのキュー"""
      +
      +    def __init__(self, size: int):
      +        """コンストラクタ"""
      +        self._nums: list[int] = [0] * size  # キュー要素を格納する配列
      +        self._front: int = 0  # 先頭ポインタ。先頭要素を指す
      +        self._size: int = 0  # キューの長さ
      +
      +    def capacity(self) -> int:
      +        """キューの容量を取得"""
      +        return len(self._nums)
      +
      +    def size(self) -> int:
      +        """キューの長さを取得"""
      +        return self._size
      +
      +    def is_empty(self) -> bool:
      +        """キューが空かどうかを判定"""
      +        return self._size == 0
      +
      +    def push(self, num: int):
      +        """エンキュー"""
      +        if self._size == self.capacity():
      +            raise IndexError("キューがいっぱいです")
      +        # 末尾ポインタを計算し、末尾インデックス + 1 を指す
      +        # 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする
      +        rear: int = (self._front + self._size) % self.capacity()
      +        # num をキュー末尾に追加
      +        self._nums[rear] = num
      +        self._size += 1
      +
      +    def pop(self) -> int:
      +        """デキュー"""
      +        num: int = self.peek()
      +        # 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す
      +        self._front = (self._front + 1) % self.capacity()
      +        self._size -= 1
      +        return num
      +
      +    def peek(self) -> int:
      +        """キュー先頭の要素にアクセス"""
      +        if self.is_empty():
      +            raise IndexError("キューが空です")
      +        return self._nums[self._front]
      +
      +    def to_list(self) -> list[int]:
      +        """表示用のリストを返す"""
      +        res = [0] * self.size()
      +        j: int = self._front
      +        for i in range(self.size()):
      +            res[i] = self._nums[(j % self.capacity())]
      +            j += 1
      +        return res
       
      -
      array_queue.cpp
      /* 循環配列に基づくキュークラス */
      -class ArrayQueue {
      -  private:
      -    int *nums;       // キュー要素を格納する配列
      -    int front;       // 先頭ポインタ、先頭要素を指す
      -    int queSize;     // キューの長さ
      -    int queCapacity; // キューの容量
      -
      -  public:
      -    ArrayQueue(int capacity) {
      -        // 配列を初期化
      -        nums = new int[capacity];
      -        queCapacity = capacity;
      -        front = queSize = 0;
      -    }
      -
      -    ~ArrayQueue() {
      -        delete[] nums;
      -    }
      -
      -    /* キューの容量を取得 */
      -    int capacity() {
      -        return queCapacity;
      -    }
      -
      -    /* キューの長さを取得 */
      -    int size() {
      -        return queSize;
      -    }
      -
      -    /* キューが空かどうかを判定 */
      -    bool isEmpty() {
      -        return size() == 0;
      -    }
      -
      -    /* エンキュー */
      -    void push(int num) {
      -        if (queSize == queCapacity) {
      -            cout << "Queue is full" << endl;
      -            return;
      -        }
      -        // 末尾ポインタを計算、末尾インデックス + 1を指す
      -        // 剰余演算を使用して末尾ポインタが配列の末尾から先頭に戻るようにラップ
      -        int rear = (front + queSize) % queCapacity;
      -        // numを末尾に追加
      -        nums[rear] = num;
      -        queSize++;
      -    }
      -
      -    /* デキュー */
      -    int pop() {
      -        int num = peek();
      -        // 先頭ポインタを1つ後ろに移動、末尾を超えた場合は配列の先頭に戻る
      -        front = (front + 1) % queCapacity;
      -        queSize--;
      -        return num;
      -    }
      -
      -    /* 先頭要素にアクセス */
      -    int peek() {
      -        if (isEmpty())
      -            throw out_of_range("Queue is empty");
      -        return nums[front];
      -    }
      -
      -    /* 配列をVectorに変換して返却 */
      -    vector<int> toVector() {
      -        // 有効な長さ範囲内の要素のみを変換
      -        vector<int> arr(queSize);
      -        for (int i = 0, j = front; i < queSize; i++, j++) {
      -            arr[i] = nums[j % queCapacity];
      -        }
      -        return arr;
      -    }
      -};
      -
      -
      -
      -
      array_queue.java
      /* 配列に基づくキュークラス */
      -class ArrayQueue {
      -    private int[] nums; // 要素を格納する配列
      -    private int front; // キューヘッドポインタ、最初の要素を指す
      -    private int queSize; // キューの長さ
      -
      -    public ArrayQueue(int capacity) {
      -        nums = new int[capacity];
      -        front = queSize = 0;
      -    }
      -
      -    /* キューの容量を取得 */
      -    public int capacity() {
      -        return nums.length;
      +
      array_queue.cpp
      /* 循環配列ベースのキュー */
      +class ArrayQueue {
      +  private:
      +    int *nums;       // キュー要素を格納する配列
      +    int front;       // 先頭ポインタ。先頭要素を指す
      +    int queSize;     // キューの長さ
      +    int queCapacity; // キューの容量
      +
      +  public:
      +    ArrayQueue(int capacity) {
      +        // 配列を初期化
      +        nums = new int[capacity];
      +        queCapacity = capacity;
      +        front = queSize = 0;
           }
       
      -    /* キューの長さを取得 */
      -    public int size() {
      -        return queSize;
      -    }
      -
      -    /* キューが空かどうかを判定 */
      -    public boolean isEmpty() {
      -        return queSize == 0;
      -    }
      -
      -    /* エンキュー */
      -    public void push(int num) {
      -        if (queSize == capacity()) {
      -            System.out.println("キューが満杯です");
      -            return;
      -        }
      -        // リアポインタを計算:front + queSize
      -        // モジュロ操作により rear が配列の長さを超えることを回避
      -        int rear = (front + queSize) % capacity();
      -        // 要素をキューリアに追加
      -        nums[rear] = num;
      -        queSize++;
      -    }
      -
      -    /* デキュー */
      -    public int pop() {
      -        int num = peek();
      -        // キューヘッドポインタを後ろに1つ移動、モジュロ操作により範囲を超えることを回避
      -        front = (front + 1) % capacity();
      -        queSize--;
      -        return num;
      +    ~ArrayQueue() {
      +        delete[] nums;
      +    }
      +
      +    /* キューの容量を取得 */
      +    int capacity() {
      +        return queCapacity;
      +    }
      +
      +    /* キューの長さを取得 */
      +    int size() {
      +        return queSize;
      +    }
      +
      +    /* キューが空かどうかを判定 */
      +    bool isEmpty() {
      +        return size() == 0;
      +    }
      +
      +    /* エンキュー */
      +    void push(int num) {
      +        if (queSize == queCapacity) {
      +            cout << "キューがいっぱいです" << endl;
      +            return;
      +        }
      +        // 末尾ポインタを計算し、末尾インデックス + 1 を指す
      +        // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする
      +        int rear = (front + queSize) % queCapacity;
      +        // num をキュー末尾に追加
      +        nums[rear] = num;
      +        queSize++;
           }
       
      -    /* キューヘッド要素にアクセス */
      -    public int peek() {
      -        if (isEmpty())
      -            throw new IndexOutOfBoundsException();
      -        return nums[front];
      -    }
      -
      -    /* 配列を返す */
      -    public int[] toArray() {
      -        // front から開始して queSize 個の要素のみをコピー
      -        int[] res = new int[queSize];
      -        for (int i = 0, j = front; i < queSize; i++, j++) {
      -            res[i] = nums[j % capacity()];
      -        }
      -        return res;
      -    }
      -}
      +    /* デキュー */
      +    int pop() {
      +        int num = peek();
      +        // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す
      +        front = (front + 1) % queCapacity;
      +        queSize--;
      +        return num;
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    int peek() {
      +        if (isEmpty())
      +            throw out_of_range("キューが空です");
      +        return nums[front];
      +    }
      +
      +    /* 配列を Vector に変換して返す */
      +    vector<int> toVector() {
      +        // 有効長の範囲内のリスト要素のみを変換
      +        vector<int> arr(queSize);
      +        for (int i = 0, j = front; i < queSize; i++, j++) {
      +            arr[i] = nums[j % queCapacity];
      +        }
      +        return arr;
      +    }
      +};
       
      -
      array_queue.cs
      [class]{ArrayQueue}-[func]{}
      +
      array_queue.java
      /* 循環配列ベースのキュー */
      +class ArrayQueue {
      +    private int[] nums; // キュー要素を格納する配列
      +    private int front; // 先頭ポインタ。先頭要素を指す
      +    private int queSize; // キューの長さ
      +
      +    public ArrayQueue(int capacity) {
      +        nums = new int[capacity];
      +        front = queSize = 0;
      +    }
      +
      +    /* キューの容量を取得 */
      +    public int capacity() {
      +        return nums.length;
      +    }
      +
      +    /* キューの長さを取得 */
      +    public int size() {
      +        return queSize;
      +    }
      +
      +    /* キューが空かどうかを判定 */
      +    public boolean isEmpty() {
      +        return queSize == 0;
      +    }
      +
      +    /* エンキュー */
      +    public void push(int num) {
      +        if (queSize == capacity()) {
      +            System.out.println("キューは満杯です");
      +            return;
      +        }
      +        // 末尾ポインタを計算し、末尾インデックス + 1 を指す
      +        // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする
      +        int rear = (front + queSize) % capacity();
      +        // num をキュー末尾に追加
      +        nums[rear] = num;
      +        queSize++;
      +    }
      +
      +    /* デキュー */
      +    public int pop() {
      +        int num = peek();
      +        // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す
      +        front = (front + 1) % capacity();
      +        queSize--;
      +        return num;
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    public int peek() {
      +        if (isEmpty())
      +            throw new IndexOutOfBoundsException();
      +        return nums[front];
      +    }
      +
      +    /* 配列を返す */
      +    public int[] toArray() {
      +        // 有効長の範囲内のリスト要素のみを変換
      +        int[] res = new int[queSize];
      +        for (int i = 0, j = front; i < queSize; i++, j++) {
      +            res[i] = nums[j % capacity()];
      +        }
      +        return res;
      +    }
      +}
       
      -
      array_queue.go
      [class]{arrayQueue}-[func]{}
      +
      array_queue.cs
      /* 循環配列ベースのキュー */
      +class ArrayQueue {
      +    int[] nums;  // キュー要素を格納する配列
      +    int front;   // 先頭ポインタ。先頭要素を指す
      +    int queSize; // キューの長さ
      +
      +    public ArrayQueue(int capacity) {
      +        nums = new int[capacity];
      +        front = queSize = 0;
      +    }
      +
      +    /* キューの容量を取得 */
      +    int Capacity() {
      +        return nums.Length;
      +    }
      +
      +    /* キューの長さを取得 */
      +    public int Size() {
      +        return queSize;
      +    }
      +
      +    /* キューが空かどうかを判定 */
      +    public bool IsEmpty() {
      +        return queSize == 0;
      +    }
      +
      +    /* エンキュー */
      +    public void Push(int num) {
      +        if (queSize == Capacity()) {
      +            Console.WriteLine("キューは満杯です");
      +            return;
      +        }
      +        // 末尾ポインタを計算し、末尾インデックス + 1 を指す
      +        // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする
      +        int rear = (front + queSize) % Capacity();
      +        // num をキュー末尾に追加
      +        nums[rear] = num;
      +        queSize++;
      +    }
      +
      +    /* デキュー */
      +    public int Pop() {
      +        int num = Peek();
      +        // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す
      +        front = (front + 1) % Capacity();
      +        queSize--;
      +        return num;
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    public int Peek() {
      +        if (IsEmpty())
      +            throw new Exception();
      +        return nums[front];
      +    }
      +
      +    /* 配列を返す */
      +    public int[] ToArray() {
      +        // 有効長の範囲内のリスト要素のみを変換
      +        int[] res = new int[queSize];
      +        for (int i = 0, j = front; i < queSize; i++, j++) {
      +            res[i] = nums[j % this.Capacity()];
      +        }
      +        return res;
      +    }
      +}
       
      -
      array_queue.swift
      [class]{ArrayQueue}-[func]{}
      +
      array_queue.go
      /* 循環配列ベースのキュー */
      +type arrayQueue struct {
      +    nums        []int // キュー要素を格納する配列
      +    front       int   // 先頭ポインタ。先頭要素を指す
      +    queSize     int   // キューの長さ
      +    queCapacity int   // キュー容量(格納できる要素数の上限)
      +}
      +
      +/* キューを初期化 */
      +func newArrayQueue(queCapacity int) *arrayQueue {
      +    return &arrayQueue{
      +        nums:        make([]int, queCapacity),
      +        queCapacity: queCapacity,
      +        front:       0,
      +        queSize:     0,
      +    }
      +}
      +
      +/* キューの長さを取得 */
      +func (q *arrayQueue) size() int {
      +    return q.queSize
      +}
      +
      +/* キューが空かどうかを判定 */
      +func (q *arrayQueue) isEmpty() bool {
      +    return q.queSize == 0
      +}
      +
      +/* エンキュー */
      +func (q *arrayQueue) push(num int) {
      +    // rear == queCapacity のときキューは満杯
      +    if q.queSize == q.queCapacity {
      +        return
      +    }
      +    // 末尾ポインタを計算し、末尾インデックス + 1 を指す
      +    // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする
      +    rear := (q.front + q.queSize) % q.queCapacity
      +    // num をキュー末尾に追加
      +    q.nums[rear] = num
      +    q.queSize++
      +}
      +
      +/* デキュー */
      +func (q *arrayQueue) pop() any {
      +    num := q.peek()
      +    if num == nil {
      +        return nil
      +    }
      +
      +    // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す
      +    q.front = (q.front + 1) % q.queCapacity
      +    q.queSize--
      +    return num
      +}
      +
      +/* キュー先頭の要素にアクセス */
      +func (q *arrayQueue) peek() any {
      +    if q.isEmpty() {
      +        return nil
      +    }
      +    return q.nums[q.front]
      +}
      +
      +/* 表示用に Slice を取得 */
      +func (q *arrayQueue) toSlice() []int {
      +    rear := (q.front + q.queSize)
      +    if rear >= q.queCapacity {
      +        rear %= q.queCapacity
      +        return append(q.nums[q.front:], q.nums[:rear]...)
      +    }
      +    return q.nums[q.front:rear]
      +}
       
      -
      array_queue.js
      [class]{ArrayQueue}-[func]{}
      +
      array_queue.swift
      /* 循環配列ベースのキュー */
      +class ArrayQueue {
      +    private var nums: [Int] // キュー要素を格納する配列
      +    private var front: Int // 先頭ポインタ。先頭要素を指す
      +    private var _size: Int // キューの長さ
      +
      +    init(capacity: Int) {
      +        // 配列を初期化
      +        nums = Array(repeating: 0, count: capacity)
      +        front = 0
      +        _size = 0
      +    }
      +
      +    /* キューの容量を取得 */
      +    func capacity() -> Int {
      +        nums.count
      +    }
      +
      +    /* キューの長さを取得 */
      +    func size() -> Int {
      +        _size
      +    }
      +
      +    /* キューが空かどうかを判定 */
      +    func isEmpty() -> Bool {
      +        size() == 0
      +    }
      +
      +    /* エンキュー */
      +    func push(num: Int) {
      +        if size() == capacity() {
      +            print("キューがいっぱいです")
      +            return
      +        }
      +        // 末尾ポインタを計算し、末尾インデックス + 1 を指す
      +        // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする
      +        let rear = (front + size()) % capacity()
      +        // num をキュー末尾に追加
      +        nums[rear] = num
      +        _size += 1
      +    }
      +
      +    /* デキュー */
      +    @discardableResult
      +    func pop() -> Int {
      +        let num = peek()
      +        // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す
      +        front = (front + 1) % capacity()
      +        _size -= 1
      +        return num
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    func peek() -> Int {
      +        if isEmpty() {
      +            fatalError("キューが空です")
      +        }
      +        return nums[front]
      +    }
      +
      +    /* 配列を返す */
      +    func toArray() -> [Int] {
      +        // 有効長の範囲内のリスト要素のみを変換
      +        (front ..< front + size()).map { nums[$0 % capacity()] }
      +    }
      +}
       
      -
      array_queue.ts
      [class]{ArrayQueue}-[func]{}
      +
      array_queue.js
      /* 循環配列ベースのキュー */
      +class ArrayQueue {
      +    #nums; // キュー要素を格納する配列
      +    #front = 0; // 先頭ポインタ。先頭要素を指す
      +    #queSize = 0; // キューの長さ
      +
      +    constructor(capacity) {
      +        this.#nums = new Array(capacity);
      +    }
      +
      +    /* キューの容量を取得 */
      +    get capacity() {
      +        return this.#nums.length;
      +    }
      +
      +    /* キューの長さを取得 */
      +    get size() {
      +        return this.#queSize;
      +    }
      +
      +    /* キューが空かどうかを判定 */
      +    isEmpty() {
      +        return this.#queSize === 0;
      +    }
      +
      +    /* エンキュー */
      +    push(num) {
      +        if (this.size === this.capacity) {
      +            console.log('キューがいっぱいです');
      +            return;
      +        }
      +        // 末尾ポインタを計算し、末尾インデックス + 1 を指す
      +        // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする
      +        const rear = (this.#front + this.size) % this.capacity;
      +        // num をキュー末尾に追加
      +        this.#nums[rear] = num;
      +        this.#queSize++;
      +    }
      +
      +    /* デキュー */
      +    pop() {
      +        const num = this.peek();
      +        // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す
      +        this.#front = (this.#front + 1) % this.capacity;
      +        this.#queSize--;
      +        return num;
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    peek() {
      +        if (this.isEmpty()) throw new Error('キューが空です');
      +        return this.#nums[this.#front];
      +    }
      +
      +    /* Array を返す */
      +    toArray() {
      +        // 有効長の範囲内のリスト要素のみを変換
      +        const arr = new Array(this.size);
      +        for (let i = 0, j = this.#front; i < this.size; i++, j++) {
      +            arr[i] = this.#nums[j % this.capacity];
      +        }
      +        return arr;
      +    }
      +}
       
      -
      array_queue.dart
      [class]{ArrayQueue}-[func]{}
      +
      array_queue.ts
      /* 循環配列ベースのキュー */
      +class ArrayQueue {
      +    private nums: number[]; // キュー要素を格納する配列
      +    private front: number; // 先頭ポインタ。先頭要素を指す
      +    private queSize: number; // キューの長さ
      +
      +    constructor(capacity: number) {
      +        this.nums = new Array(capacity);
      +        this.front = this.queSize = 0;
      +    }
      +
      +    /* キューの容量を取得 */
      +    get capacity(): number {
      +        return this.nums.length;
      +    }
      +
      +    /* キューの長さを取得 */
      +    get size(): number {
      +        return this.queSize;
      +    }
      +
      +    /* キューが空かどうかを判定 */
      +    isEmpty(): boolean {
      +        return this.queSize === 0;
      +    }
      +
      +    /* エンキュー */
      +    push(num: number): void {
      +        if (this.size === this.capacity) {
      +            console.log('キューは満杯です');
      +            return;
      +        }
      +        // 末尾ポインタを計算し、末尾インデックス + 1 を指す
      +        // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする
      +        const rear = (this.front + this.queSize) % this.capacity;
      +        // num をキュー末尾に追加
      +        this.nums[rear] = num;
      +        this.queSize++;
      +    }
      +
      +    /* デキュー */
      +    pop(): number {
      +        const num = this.peek();
      +        // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す
      +        this.front = (this.front + 1) % this.capacity;
      +        this.queSize--;
      +        return num;
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    peek(): number {
      +        if (this.isEmpty()) throw new Error('キューが空です');
      +        return this.nums[this.front];
      +    }
      +
      +    /* Array を返す */
      +    toArray(): number[] {
      +        // 有効長の範囲内のリスト要素のみを変換
      +        const arr = new Array(this.size);
      +        for (let i = 0, j = this.front; i < this.size; i++, j++) {
      +            arr[i] = this.nums[j % this.capacity];
      +        }
      +        return arr;
      +    }
      +}
       
      -
      array_queue.rs
      [class]{ArrayQueue}-[func]{}
      +
      array_queue.dart
      /* 循環配列ベースのキュー */
      +class ArrayQueue {
      +  late List<int> _nums; // キュー要素を格納する配列
      +  late int _front; // 先頭ポインタ。先頭要素を指す
      +  late int _queSize; // キューの長さ
      +
      +  ArrayQueue(int capacity) {
      +    _nums = List.filled(capacity, 0);
      +    _front = _queSize = 0;
      +  }
      +
      +  /* キューの容量を取得 */
      +  int capaCity() {
      +    return _nums.length;
      +  }
      +
      +  /* キューの長さを取得 */
      +  int size() {
      +    return _queSize;
      +  }
      +
      +  /* キューが空かどうかを判定 */
      +  bool isEmpty() {
      +    return _queSize == 0;
      +  }
      +
      +  /* エンキュー */
      +  void push(int _num) {
      +    if (_queSize == capaCity()) {
      +      throw Exception("キューは満杯です");
      +    }
      +    // 末尾ポインタを計算し、末尾インデックス + 1 を指す
      +    // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする
      +    int rear = (_front + _queSize) % capaCity();
      +    // _num をキュー末尾に追加
      +    _nums[rear] = _num;
      +    _queSize++;
      +  }
      +
      +  /* デキュー */
      +  int pop() {
      +    int _num = peek();
      +    // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す
      +    _front = (_front + 1) % capaCity();
      +    _queSize--;
      +    return _num;
      +  }
      +
      +  /* キュー先頭の要素にアクセス */
      +  int peek() {
      +    if (isEmpty()) {
      +      throw Exception("キューが空です");
      +    }
      +    return _nums[_front];
      +  }
      +
      +  /* Array を返す */
      +  List<int> toArray() {
      +    // 有効長の範囲内のリスト要素のみを変換
      +    final List<int> res = List.filled(_queSize, 0);
      +    for (int i = 0, j = _front; i < _queSize; i++, j++) {
      +      res[i] = _nums[j % capaCity()];
      +    }
      +    return res;
      +  }
      +}
       
      -
      array_queue.c
      [class]{ArrayQueue}-[func]{}
      +
      array_queue.rs
      /* 循環配列ベースのキュー */
      +struct ArrayQueue<T> {
      +    nums: Vec<T>,      // キュー要素を格納する配列
      +    front: i32,        // 先頭ポインタ。先頭要素を指す
      +    que_size: i32,     // キューの長さ
      +    que_capacity: i32, // キューの容量
      +}
      +
      +impl<T: Copy + Default> ArrayQueue<T> {
      +    /* コンストラクタ */
      +    fn new(capacity: i32) -> ArrayQueue<T> {
      +        ArrayQueue {
      +            nums: vec![T::default(); capacity as usize],
      +            front: 0,
      +            que_size: 0,
      +            que_capacity: capacity,
      +        }
      +    }
      +
      +    /* キューの容量を取得 */
      +    fn capacity(&self) -> i32 {
      +        self.que_capacity
      +    }
      +
      +    /* キューの長さを取得 */
      +    fn size(&self) -> i32 {
      +        self.que_size
      +    }
      +
      +    /* キューが空かどうかを判定 */
      +    fn is_empty(&self) -> bool {
      +        self.que_size == 0
      +    }
      +
      +    /* エンキュー */
      +    fn push(&mut self, num: T) {
      +        if self.que_size == self.capacity() {
      +            println!("キューがいっぱいです");
      +            return;
      +        }
      +        // 末尾ポインタを計算し、末尾インデックス + 1 を指す
      +        // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする
      +        let rear = (self.front + self.que_size) % self.que_capacity;
      +        // num をキュー末尾に追加
      +        self.nums[rear as usize] = num;
      +        self.que_size += 1;
      +    }
      +
      +    /* デキュー */
      +    fn pop(&mut self) -> T {
      +        let num = self.peek();
      +        // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す
      +        self.front = (self.front + 1) % self.que_capacity;
      +        self.que_size -= 1;
      +        num
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    fn peek(&self) -> T {
      +        if self.is_empty() {
      +            panic!("index out of bounds");
      +        }
      +        self.nums[self.front as usize]
      +    }
      +
      +    /* 配列を返す */
      +    fn to_vector(&self) -> Vec<T> {
      +        let cap = self.que_capacity;
      +        let mut j = self.front;
      +        let mut arr = vec![T::default(); cap as usize];
      +        for i in 0..self.que_size {
      +            arr[i as usize] = self.nums[(j % cap) as usize];
      +            j += 1;
      +        }
      +        arr
      +    }
      +}
       
      -
      array_queue.kt
      [class]{ArrayQueue}-[func]{}
      +
      array_queue.c
      /* 循環配列ベースのキュー */
      +typedef struct {
      +    int *nums;       // キュー要素を格納する配列
      +    int front;       // 先頭ポインタ。先頭要素を指す
      +    int queSize;     // 末尾ポインタ。キューの末尾 + 1 を指す
      +    int queCapacity; // キューの容量
      +} ArrayQueue;
      +
      +/* コンストラクタ */
      +ArrayQueue *newArrayQueue(int capacity) {
      +    ArrayQueue *queue = (ArrayQueue *)malloc(sizeof(ArrayQueue));
      +    // 配列を初期化
      +    queue->queCapacity = capacity;
      +    queue->nums = (int *)malloc(sizeof(int) * queue->queCapacity);
      +    queue->front = queue->queSize = 0;
      +    return queue;
      +}
      +
      +/* デストラクタ */
      +void delArrayQueue(ArrayQueue *queue) {
      +    free(queue->nums);
      +    free(queue);
      +}
      +
      +/* キューの容量を取得 */
      +int capacity(ArrayQueue *queue) {
      +    return queue->queCapacity;
      +}
      +
      +/* キューの長さを取得 */
      +int size(ArrayQueue *queue) {
      +    return queue->queSize;
      +}
      +
      +/* キューが空かどうかを判定 */
      +bool empty(ArrayQueue *queue) {
      +    return queue->queSize == 0;
      +}
      +
      +/* キュー先頭の要素にアクセス */
      +int peek(ArrayQueue *queue) {
      +    assert(size(queue) != 0);
      +    return queue->nums[queue->front];
      +}
      +
      +/* エンキュー */
      +void push(ArrayQueue *queue, int num) {
      +    if (size(queue) == capacity(queue)) {
      +        printf("キューは満杯です\r\n");
      +        return;
      +    }
      +    // 末尾ポインタを計算し、末尾インデックス + 1 を指す
      +    // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする
      +    int rear = (queue->front + queue->queSize) % queue->queCapacity;
      +    // num をキュー末尾に追加
      +    queue->nums[rear] = num;
      +    queue->queSize++;
      +}
      +
      +/* デキュー */
      +int pop(ArrayQueue *queue) {
      +    int num = peek(queue);
      +    // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す
      +    queue->front = (queue->front + 1) % queue->queCapacity;
      +    queue->queSize--;
      +    return num;
      +}
      +
      +/* 出力用の配列を返す */
      +int *toArray(ArrayQueue *queue, int *queSize) {
      +    *queSize = queue->queSize;
      +    int *res = (int *)calloc(queue->queSize, sizeof(int));
      +    int j = queue->front;
      +    for (int i = 0; i < queue->queSize; i++) {
      +        res[i] = queue->nums[j % queue->queCapacity];
      +        j++;
      +    }
      +    return res;
      +}
       
      -
      array_queue.rb
      [class]{ArrayQueue}-[func]{}
      +
      array_queue.kt
      /* 循環配列ベースのキュー */
      +class ArrayQueue(capacity: Int) {
      +    private val nums: IntArray = IntArray(capacity) // キュー要素を格納する配列
      +    private var front: Int = 0 // 先頭ポインタ。先頭要素を指す
      +    private var queSize: Int = 0 // キューの長さ
      +
      +    /* キューの容量を取得 */
      +    fun capacity(): Int {
      +        return nums.size
      +    }
      +
      +    /* キューの長さを取得 */
      +    fun size(): Int {
      +        return queSize
      +    }
      +
      +    /* キューが空かどうかを判定 */
      +    fun isEmpty(): Boolean {
      +        return queSize == 0
      +    }
      +
      +    /* エンキュー */
      +    fun push(num: Int) {
      +        if (queSize == capacity()) {
      +            println("キューは満杯です")
      +            return
      +        }
      +        // 末尾ポインタを計算し、末尾インデックス + 1 を指す
      +        // 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする
      +        val rear = (front + queSize) % capacity()
      +        // num をキュー末尾に追加
      +        nums[rear] = num
      +        queSize++
      +    }
      +
      +    /* デキュー */
      +    fun pop(): Int {
      +        val num = peek()
      +        // 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す
      +        front = (front + 1) % capacity()
      +        queSize--
      +        return num
      +    }
      +
      +    /* キュー先頭の要素にアクセス */
      +    fun peek(): Int {
      +        if (isEmpty()) throw IndexOutOfBoundsException()
      +        return nums[front]
      +    }
      +
      +    /* 配列を返す */
      +    fun toArray(): IntArray {
      +        // 有効長の範囲内のリスト要素のみを変換
      +        val res = IntArray(queSize)
      +        var i = 0
      +        var j = front
      +        while (i < queSize) {
      +            res[i] = nums[j % capacity()]
      +            i++
      +            j++
      +        }
      +        return res
      +    }
      +}
      +
      +
      +
      +
      array_queue.rb
      ### 循環配列で実装したキュー ###
      +class ArrayQueue
      +  ### キューの長さを取得 ###
      +  attr_reader :size
      +
      +  ### コンストラクタ ###
      +  def initialize(size)
      +    @nums = Array.new(size, 0) # キュー要素を格納する配列
      +    @front = 0 # 先頭ポインタ。先頭要素を指す
      +    @size = 0 # キューの長さ
      +  end
      +
      +  ### キューの容量を取得 ###
      +  def capacity
      +    @nums.length
      +  end
      +
      +  ### キューが空か判定 ###
      +  def is_empty?
      +    size.zero?
      +  end
      +
      +  ### エンキュー ###
      +  def push(num)
      +    raise IndexError, 'キューがいっぱいです' if size == capacity
      +
      +    # 末尾ポインタを計算し、末尾インデックス + 1 を指す
      +    # 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする
      +    rear = (@front + size) % capacity
      +    # num をキュー末尾に追加
      +    @nums[rear] = num
      +    @size += 1
      +  end
      +
      +  ### デキュー ###
      +  def pop
      +    num = peek
      +    # 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す
      +    @front = (@front + 1) % capacity
      +    @size -= 1
      +    num
      +  end
      +
      +  ### 先頭要素にアクセス ###
      +  def peek
      +    raise IndexError, 'キューは空です' if is_empty?
      +
      +    @nums[@front]
      +  end
      +
      +  ### 表示用のリストを返す ###
      +  def to_array
      +    res = Array.new(size, 0)
      +    j = @front
      +
      +    for i in 0...size
      +      res[i] = @nums[j % capacity]
      +      j += 1
      +    end
      +
      +    res
      +  end
      +end
       
      -

      上記のキュー実装にはまだ制限があります:長さが固定されています。しかし、この問題は解決が困難ではありません。配列を必要に応じて自動拡張できる動的配列に置き換えることができます。興味のある読者は自分で実装してみてください。

      -

      2つの実装の比較はスタックの場合と一貫しており、ここでは繰り返しません。

      +
      +コードの可視化 +

      +

      +
      +

      上記の実装によるキューにも制約があり、長さを可変にできません。しかし、この問題の解決は難しくなく、配列を動的配列に置き換えれば容量拡張の仕組みを導入できます。興味があれば自分で実装してみてください。

      +

      2 つの実装の比較に関する結論はスタックの場合と同じなので、ここでは繰り返しません。

      5.2.3   キューの典型的な応用

        -
      • Amazonの注文:買い物客が注文を行った後、これらの注文はキューに参加し、システムは順番に処理します。独身の日などのイベント中は、短時間で大量の注文が生成され、高い同時実行性がエンジニアにとって重要な課題となります。
      • -
      • 様々なToDoリスト:「先着順」機能が必要なシナリオ、例えばプリンターのタスクキューやレストランの配達キューなど、キューで処理順序を効果的に維持できます。
      • +
      • 淘宝の注文。購入者が注文すると、その注文はキューに追加され、システムは順番に従って注文を処理します。ダブルイレブンの期間には短時間で膨大な注文が発生するため、高並行性がエンジニアにとって重点的に解決すべき課題になります。
      • +
      • 各種の待機事項。先着順の機能を実現する必要があるあらゆる場面、たとえばプリンターのジョブキューや飲食店の配膳キューなどでは、キューによって処理順序を効果的に維持できます。
      diff --git a/ja/chapter_stack_and_queue/stack/index.html b/ja/chapter_stack_and_queue/stack/index.html index cfa421119..88d4a75ba 100644 --- a/ja/chapter_stack_and_queue/stack/index.html +++ b/ja/chapter_stack_and_queue/stack/index.html @@ -6,7 +6,7 @@ - + @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1503,7 +1503,7 @@ - 5.1.1   スタックの一般的な操作 + 5.1.1   スタックの基本操作 @@ -1526,7 +1526,7 @@ - 1.   連結リストベースの実装 + 1.   連結リストによる実装 @@ -1537,7 +1537,7 @@ - 2.   配列ベースの実装 + 2.   配列による実装 @@ -1713,7 +1713,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1735,7 +1735,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1757,7 +1757,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -2064,7 +2064,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2227,7 +2227,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2613,7 +2613,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2669,7 +2669,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2697,7 +2697,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -3235,7 +3235,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3291,7 +3291,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3319,7 +3319,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3512,7 +3512,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3540,7 +3540,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3681,7 +3681,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3709,7 +3709,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3737,7 +3737,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3765,7 +3765,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3958,7 +3958,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4203,7 +4203,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4349,7 +4349,7 @@ - 5.1.1   スタックの一般的な操作 + 5.1.1   スタックの基本操作 @@ -4372,7 +4372,7 @@ - 1.   連結リストベースの実装 + 1.   連結リストによる実装 @@ -4383,7 +4383,7 @@ - 2.   配列ベースの実装 + 2.   配列による実装 @@ -4455,15 +4455,15 @@

      5.1   スタック

      -

      スタックは、後入先出(LIFO)の原則に従う線形データ構造です。

      -

      スタックをテーブル上の皿の山に例えることができます。底の皿にアクセスするには、まず上の皿を取り除く必要があります。皿を様々な種類の要素(整数、文字、オブジェクトなど)に置き換えることで、スタックと呼ばれるデータ構造を得ることができます。

      -

      下図に示すように、要素の山の上部を「スタックのトップ」、下部を「スタックのボトム」と呼びます。スタックのトップに要素を追加する操作を「プッシュ」、トップ要素を削除する操作を「ポップ」と呼びます。

      -

      スタックの後入先出ルール

      -

      図 5-1   スタックの後入先出ルール

      +

      スタック(stack)は、後入れ先出しの論理に従う線形データ構造です。

      +

      スタックは机の上に積まれた皿の山にたとえられます。1回に1枚の皿しか動かせないとすると、いちばん下の皿を取り出すには、上にある皿を順番にどかす必要があります。この皿をさまざまな型の要素(整数、文字、オブジェクトなど)に置き換えたものが、スタックというデータ構造です。

      +

      下図のように、積み重なった要素の上端を「スタックトップ」、下端を「スタックボトム」と呼びます。要素をスタックトップに追加する操作を「プッシュ」、スタックトップの要素を削除する操作を「ポップ」と呼びます。

      +

      スタックの後入れ先出しの規則

      +

      図 5-1   スタックの後入れ先出しの規則

      -

      5.1.1   スタックの一般的な操作

      -

      スタックの一般的な操作を下表に示します。具体的なメソッド名は使用するプログラミング言語によって異なります。ここでは、例としてpush()pop()peek()を使用します。

      -

      表 5-1   スタック操作の効率

      +

      5.1.1   スタックの基本操作

      +

      スタックの基本操作を以下の表に示します。具体的なメソッド名は使用するプログラミング言語によって異なります。ここでは、一般的な push()pop()peek() を例に挙げます。

      +

      表 5-1   スタックの操作効率

      @@ -4477,47 +4477,47 @@ - + - + - +
      push()要素をスタックにプッシュ(トップに追加)要素をプッシュする(スタックトップに追加) \(O(1)\)
      pop()スタックからトップ要素をポップスタックトップの要素をポップする \(O(1)\)
      peek()スタックのトップ要素にアクセススタックトップの要素にアクセスする \(O(1)\)
      -

      通常、プログラミング言語に組み込まれているスタッククラスを直接使用できます。ただし、一部の言語では具体的にスタッククラスを提供していない場合があります。これらの場合、言語の「配列」または「連結リスト」をスタックとして使用し、プログラムでスタックロジックに関連しない操作を無視できます。

      -
      +

      通常は、プログラミング言語に組み込まれているスタッククラスをそのまま利用できます。ただし、専用のスタッククラスが用意されていない言語もあります。その場合は、その言語の「配列」や「連結リスト」をスタックとして用い、プログラムのロジック上でスタックに無関係な操作を無視します。

      +
      stack.py
      # スタックを初期化
      -# Pythonには組み込みのスタッククラスがないため、listをスタックとして使用
      +# Python には組み込みのスタッククラスがないため、list をスタックとして使用できる
       stack: list[int] = []
       
      -# 要素をスタックにプッシュ
      +# 要素をプッシュ
       stack.append(1)
       stack.append(3)
       stack.append(2)
       stack.append(5)
       stack.append(4)
       
      -# スタックのトップ要素にアクセス
      +# スタックトップの要素にアクセス
       peek: int = stack[-1]
       
      -# スタックから要素をポップ
      +# 要素をポップ
       pop: int = stack.pop()
       
       # スタックの長さを取得
       size: int = len(stack)
       
      -# スタックが空かどうかチェック
      +# 空かどうかを判定
       is_empty: bool = len(stack) == 0
       
      @@ -4525,23 +4525,23 @@
      stack.cpp
      /* スタックを初期化 */
       stack<int> stack;
       
      -/* 要素をスタックにプッシュ */
      +/* 要素をプッシュ */
       stack.push(1);
       stack.push(3);
       stack.push(2);
       stack.push(5);
       stack.push(4);
       
      -/* スタックのトップ要素にアクセス */
      +/* スタックトップの要素にアクセス */
       int top = stack.top();
       
      -/* スタックから要素をポップ */
      +/* 要素をポップ */
       stack.pop(); // 戻り値なし
       
       /* スタックの長さを取得 */
       int size = stack.size();
       
      -/* スタックが空かどうかチェック */
      +/* 空かどうかを判定 */
       bool empty = stack.empty();
       
      @@ -4549,23 +4549,23 @@
      stack.java
      /* スタックを初期化 */
       Stack<Integer> stack = new Stack<>();
       
      -/* 要素をスタックにプッシュ */
      +/* 要素をプッシュ */
       stack.push(1);
       stack.push(3);
       stack.push(2);
       stack.push(5);
       stack.push(4);
       
      -/* スタックのトップ要素にアクセス */
      +/* スタックトップの要素にアクセス */
       int peek = stack.peek();
       
      -/* スタックから要素をポップ */
      +/* 要素をポップ */
       int pop = stack.pop();
       
       /* スタックの長さを取得 */
       int size = stack.size();
       
      -/* スタックが空かどうかチェック */
      +/* 空かどうかを判定 */
       boolean isEmpty = stack.isEmpty();
       
      @@ -4573,197 +4573,246 @@
      stack.cs
      /* スタックを初期化 */
       Stack<int> stack = new();
       
      -/* 要素をスタックにプッシュ */
      +/* 要素をプッシュ */
       stack.Push(1);
       stack.Push(3);
       stack.Push(2);
       stack.Push(5);
       stack.Push(4);
       
      -/* スタックのトップ要素にアクセス */
      +/* スタックトップの要素にアクセス */
       int peek = stack.Peek();
       
      -/* スタックから要素をポップ */
      +/* 要素をポップ */
       int pop = stack.Pop();
       
       /* スタックの長さを取得 */
       int size = stack.Count;
       
      -/* スタックが空かどうかチェック */
      +/* 空かどうかを判定 */
       bool isEmpty = stack.Count == 0;
       
      stack_test.go
      /* スタックを初期化 */
      -// Goでは、Sliceをスタックとして使用することが推奨されます
      +// Go では、Slice をスタックとして使うのが一般的
       var stack []int
       
      -/* 要素をスタックにプッシュ */
      +/* 要素をプッシュ */
       stack = append(stack, 1)
       stack = append(stack, 3)
       stack = append(stack, 2)
       stack = append(stack, 5)
       stack = append(stack, 4)
       
      -/* スタックのトップ要素にアクセス */
      +/* スタックトップの要素にアクセス */
       peek := stack[len(stack)-1]
       
      -/* スタックから要素をポップ */
      +/* 要素をポップ */
       pop := stack[len(stack)-1]
       stack = stack[:len(stack)-1]
       
       /* スタックの長さを取得 */
       size := len(stack)
       
      -/* スタックが空かどうかチェック */
      +/* 空かどうかを判定 */
       isEmpty := len(stack) == 0
       
      stack.swift
      /* スタックを初期化 */
      -// Swiftには組み込みのスタッククラスがないため、Arrayをスタックとして使用
      +// Swift には組み込みのスタッククラスがないため、Array をスタックとして使用できる
       var stack: [Int] = []
       
      -/* 要素をスタックにプッシュ */
      +/* 要素をプッシュ */
       stack.append(1)
       stack.append(3)
       stack.append(2)
       stack.append(5)
       stack.append(4)
       
      -/* スタックのトップ要素にアクセス */
      +/* スタックトップの要素にアクセス */
       let peek = stack.last!
       
      -/* スタックから要素をポップ */
      +/* 要素をポップ */
       let pop = stack.removeLast()
       
       /* スタックの長さを取得 */
       let size = stack.count
       
      -/* スタックが空かどうかチェック */
      +/* 空かどうかを判定 */
       let isEmpty = stack.isEmpty
       
      stack.js
      /* スタックを初期化 */
      -// JavaScriptには組み込みのスタッククラスがないため、Arrayをスタックとして使用
      +// JavaScript には組み込みのスタッククラスがないため、Array をスタックとして使用できる
       const stack = [];
       
      -/* 要素をスタックにプッシュ */
      +/* 要素をプッシュ */
       stack.push(1);
       stack.push(3);
       stack.push(2);
       stack.push(5);
       stack.push(4);
       
      -/* スタックのトップ要素にアクセス */
      +/* スタックトップの要素にアクセス */
       const peek = stack[stack.length-1];
       
      -/* スタックから要素をポップ */
      +/* 要素をポップ */
       const pop = stack.pop();
       
       /* スタックの長さを取得 */
       const size = stack.length;
       
      -/* スタックが空かどうかチェック */
      +/* 空かどうかを判定 */
       const is_empty = stack.length === 0;
       
      stack.ts
      /* スタックを初期化 */
      -// TypeScriptには組み込みのスタッククラスがないため、Arrayをスタックとして使用
      +// TypeScript には組み込みのスタッククラスがないため、Array をスタックとして使用できる
       const stack: number[] = [];
       
      -/* 要素をスタックにプッシュ */
      +/* 要素をプッシュ */
       stack.push(1);
       stack.push(3);
       stack.push(2);
       stack.push(5);
       stack.push(4);
       
      -/* スタックのトップ要素にアクセス */
      +/* スタックトップの要素にアクセス */
       const peek = stack[stack.length - 1];
       
      -/* スタックから要素をポップ */
      +/* 要素をポップ */
       const pop = stack.pop();
       
       /* スタックの長さを取得 */
       const size = stack.length;
       
      -/* スタックが空かどうかチェック */
      +/* 空かどうかを判定 */
       const is_empty = stack.length === 0;
       
      stack.dart
      /* スタックを初期化 */
      -// Dartには組み込みのスタッククラスがないため、Listをスタックとして使用
      +// Dart には組み込みのスタッククラスがないため、List をスタックとして使用できる
       List<int> stack = [];
       
      -/* 要素をスタックにプッシュ */
      +/* 要素をプッシュ */
       stack.add(1);
       stack.add(3);
       stack.add(2);
       stack.add(5);
       stack.add(4);
       
      -/* スタックのトップ要素にアクセス */
      +/* スタックトップの要素にアクセス */
       int peek = stack.last;
       
      -/* スタックから要素をポップ */
      +/* 要素をポップ */
       int pop = stack.removeLast();
       
       /* スタックの長さを取得 */
       int size = stack.length;
       
      -/* スタックが空かどうかチェック */
      +/* 空かどうかを判定 */
       bool isEmpty = stack.isEmpty;
       
      stack.rs
      /* スタックを初期化 */
      -// Vecをスタックとして使用
      +// Vec をスタックとして使用する
       let mut stack: Vec<i32> = Vec::new();
       
      -/* 要素をスタックにプッシュ */
      +/* 要素をプッシュ */
       stack.push(1);
       stack.push(3);
       stack.push(2);
       stack.push(5);
       stack.push(4);
       
      -/* スタックのトップ要素にアクセス */
      +/* スタックトップの要素にアクセス */
       let top = stack.last().unwrap();
       
      -/* スタックから要素をポップ */
      +/* 要素をポップ */
       let pop = stack.pop().unwrap();
       
       /* スタックの長さを取得 */
       let size = stack.len();
       
      -/* スタックが空かどうかチェック */
      +/* 空かどうかを判定 */
       let is_empty = stack.is_empty();
       
      -
      stack.c
      // Cは組み込みのスタックを提供していません
      +
      stack.c
      // C には組み込みのスタックがない
       
      -
      stack.kt
      
      +
      stack.kt
      /* スタックを初期化 */
      +val stack = Stack<Int>()
      +
      +/* 要素をプッシュ */
      +stack.push(1)
      +stack.push(3)
      +stack.push(2)
      +stack.push(5)
      +stack.push(4)
      +
      +/* スタックトップの要素にアクセス */
      +val peek = stack.peek()
      +
      +/* 要素をポップ */
      +val pop = stack.pop()
      +
      +/* スタックの長さを取得 */
      +val size = stack.size
      +
      +/* 空かどうかを判定 */
      +val isEmpty = stack.isEmpty()
      +
      +
      +
      +
      stack.rb
      # スタックを初期化
      +# Ruby には組み込みのスタッククラスがないため、Array をスタックとして使用できる
      +stack = []
      +
      +# 要素をプッシュ
      +stack << 1
      +stack << 3
      +stack << 2
      +stack << 5
      +stack << 4
      +
      +# スタックトップの要素にアクセス
      +peek = stack.last
      +
      +# 要素をポップ
      +pop = stack.pop
      +
      +# スタックの長さを取得
      +size = stack.length
      +
      +# 空かどうかを判定
      +is_empty = stack.empty?
       
      +
      +実行の可視化 +

      https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E5%88%9D%E5%A7%8B%E5%8C%96%E6%A0%88%0A%20%20%20%20%23%20Python%20%E6%B2%A1%E6%9C%89%E5%86%85%E7%BD%AE%E7%9A%84%E6%A0%88%E7%B1%BB%EF%BC%8C%E5%8F%AF%E4%BB%A5%E6%8A%8A%20list%20%E5%BD%93%E4%BD%9C%E6%A0%88%E6%9D%A5%E4%BD%BF%E7%94%A8%0A%20%20%20%20stack%20%3D%20%5B%5D%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%85%A5%E6%A0%88%0A%20%20%20%20stack.append%281%29%0A%20%20%20%20stack.append%283%29%0A%20%20%20%20stack.append%282%29%0A%20%20%20%20stack.append%285%29%0A%20%20%20%20stack.append%284%29%0A%20%20%20%20print%28%22%E6%A0%88%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%AE%BF%E9%97%AE%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%0A%20%20%20%20peek%20%3D%20stack%5B-1%5D%0A%20%20%20%20print%28%22%E6%A0%88%E9%A1%B6%E5%85%83%E7%B4%A0%20peek%20%3D%22,%20peek%29%0A%0A%20%20%20%20%23%20%E5%85%83%E7%B4%A0%E5%87%BA%E6%A0%88%0A%20%20%20%20pop%20%3D%20stack.pop%28%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%85%83%E7%B4%A0%20pop%20%3D%22,%20pop%29%0A%20%20%20%20print%28%22%E5%87%BA%E6%A0%88%E5%90%8E%20stack%20%3D%22,%20stack%29%0A%0A%20%20%20%20%23%20%E8%8E%B7%E5%8F%96%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%0A%20%20%20%20size%20%3D%20len%28stack%29%0A%20%20%20%20print%28%22%E6%A0%88%E7%9A%84%E9%95%BF%E5%BA%A6%20size%20%3D%22,%20size%29%0A%0A%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%0A%20%20%20%20is_empty%20%3D%20len%28stack%29%20%3D%3D%200%0A%20%20%20%20print%28%22%E6%A0%88%E6%98%AF%E5%90%A6%E4%B8%BA%E7%A9%BA%20%3D%22,%20is_empty%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false

      +

      5.1.2   スタックの実装

      -

      スタックがどのように動作するかをより深く理解するために、自分でスタッククラスを実装してみましょう。

      -

      スタックは後入先出の原則に従うため、スタックのトップでのみ要素を追加または削除できます。しかし、配列と連結リストの両方は任意の位置で要素を追加・削除できるため、スタックは制限された配列または連結リストと見なすことができます。言い換えれば、配列や連結リストの特定の無関係な操作を「遮蔽」して、外部の動作をスタックの特性に合わせることができます。

      -

      1.   連結リストベースの実装

      -

      連結リストを使用してスタックを実装する場合、リストのヘッドノードをスタックのトップ、テールノードをスタックのボトムと考えることができます。

      -

      下図に示すように、プッシュ操作では、単に連結リストのヘッドに要素を挿入します。このノード挿入方法は「ヘッド挿入」として知られています。ポップ操作では、リストからヘッドノードを削除するだけです。

      +

      スタックの動作の仕組みをより深く理解するために、自分でスタッククラスを実装してみましょう。

      +

      スタックは後入れ先出しの原則に従うため、要素の追加や削除はスタックトップでしか行えません。一方、配列や連結リストでは任意の位置で要素を追加・削除できます。つまり、スタックは制限付きの配列または連結リストとみなせます。 言い換えると、配列や連結リストのうち無関係な操作を「隠蔽」することで、外から見た振る舞いをスタックの特性に合わせられます。

      +

      1.   連結リストによる実装

      +

      連結リストでスタックを実装する場合、連結リストの先頭ノードをスタックトップ、末尾ノードをスタックボトムとみなせます。

      +

      下図のように、プッシュ操作では要素を連結リストの先頭に挿入するだけでよく、このノード挿入方法は「頭部挿入法」と呼ばれます。ポップ操作では、先頭ノードを連結リストから削除するだけです。

      -

      連結リストによるスタック実装のプッシュとポップ操作

      +

      連結リストによるスタック実装のプッシュ・ポップ操作

      linkedlist_stack_push

      @@ -4773,230 +4822,769 @@
      -

      図 5-2   連結リストによるスタック実装のプッシュとポップ操作

      +

      図 5-2   連結リストによるスタック実装のプッシュ・ポップ操作

      -

      以下は、連結リストに基づくスタック実装のサンプルコードです:

      +

      以下は、連結リストによってスタックを実装したコード例です:

      -
      linkedlist_stack.py
      class LinkedListStack:
      -    """連結リストベースのスタッククラス"""
      -
      -    def __init__(self):
      -        """コンストラクタ"""
      -        self._peek: ListNode | None = None
      -        self._size: int = 0
      -
      -    def size(self) -> int:
      -        """スタックの長さを取得"""
      -        return self._size
      -
      -    def is_empty(self) -> bool:
      -        """スタックが空かどうかを判定"""
      -        return self._size == 0
      -
      -    def push(self, val: int):
      -        """プッシュ"""
      -        node = ListNode(val)
      -        node.next = self._peek
      -        self._peek = node
      -        self._size += 1
      -
      -    def pop(self) -> int:
      -        """ポップ"""
      -        num = self.peek()
      -        self._peek = self._peek.next
      -        self._size -= 1
      -        return num
      -
      -    def peek(self) -> int:
      -        """スタックトップ要素にアクセス"""
      -        if self.is_empty():
      -            raise IndexError("Stack is empty")
      -        return self._peek.val
      -
      -    def to_list(self) -> list[int]:
      -        """出力用のリストに変換"""
      -        arr = []
      -        node = self._peek
      -        while node:
      -            arr.append(node.val)
      -            node = node.next
      -        arr.reverse()
      -        return arr
      -
      -
      -
      -
      linkedlist_stack.cpp
      /* 連結リストに基づくスタッククラス */
      -class LinkedListStack {
      -  private:
      -    ListNode *stackTop; // 先頭ノードをスタックトップとして使用
      -    int stkSize;        // スタックの長さ
      -
      -  public:
      -    LinkedListStack() {
      -        stackTop = nullptr;
      -        stkSize = 0;
      -    }
      +
      linkedlist_stack.py
      class LinkedListStack:
      +    """連結リストベースのスタック"""
      +
      +    def __init__(self):
      +        """コンストラクタ"""
      +        self._peek: ListNode | None = None
      +        self._size: int = 0
      +
      +    def size(self) -> int:
      +        """スタックの長さを取得"""
      +        return self._size
       
      -    ~LinkedListStack() {
      -        // 連結リストを走査、ノードを削除、メモリを解放
      -        freeMemoryLinkedList(stackTop);
      -    }
      -
      -    /* スタックの長さを取得 */
      -    int size() {
      -        return stkSize;
      -    }
      -
      -    /* スタックが空かどうかを判定 */
      -    bool isEmpty() {
      -        return size() == 0;
      -    }
      -
      -    /* プッシュ */
      -    void push(int num) {
      -        ListNode *node = new ListNode(num);
      -        node->next = stackTop;
      -        stackTop = node;
      -        stkSize++;
      -    }
      -
      -    /* ポップ */
      -    int pop() {
      -        int num = top();
      -        ListNode *tmp = stackTop;
      -        stackTop = stackTop->next;
      -        // メモリを解放
      -        delete tmp;
      -        stkSize--;
      -        return num;
      -    }
      -
      -    /* スタックトップ要素にアクセス */
      -    int top() {
      -        if (isEmpty())
      -            throw out_of_range("Stack is empty");
      -        return stackTop->val;
      -    }
      -
      -    /* リストを配列に変換して返却 */
      -    vector<int> toVector() {
      -        ListNode *node = stackTop;
      -        vector<int> res(size());
      -        for (int i = res.size() - 1; i >= 0; i--) {
      -            res[i] = node->val;
      -            node = node->next;
      -        }
      -        return res;
      -    }
      -};
      +    def is_empty(self) -> bool:
      +        """スタックが空かどうかを判定"""
      +        return self._size == 0
      +
      +    def push(self, val: int):
      +        """プッシュ"""
      +        node = ListNode(val)
      +        node.next = self._peek
      +        self._peek = node
      +        self._size += 1
      +
      +    def pop(self) -> int:
      +        """ポップ"""
      +        num = self.peek()
      +        self._peek = self._peek.next
      +        self._size -= 1
      +        return num
      +
      +    def peek(self) -> int:
      +        """スタックトップの要素にアクセス"""
      +        if self.is_empty():
      +            raise IndexError("スタックが空です")
      +        return self._peek.val
      +
      +    def to_list(self) -> list[int]:
      +        """表示用にリストへ変換"""
      +        arr = []
      +        node = self._peek
      +        while node:
      +            arr.append(node.val)
      +            node = node.next
      +        arr.reverse()
      +        return arr
       
      -
      linkedlist_stack.java
      /* 連結リストに基づくスタッククラス */
      -class LinkedListStack {
      -    private ListNode stackPeek; // ヘッドノードをスタックトップとして使用
      -    private int stkSize = 0; // スタックの長さ
      -
      -    public LinkedListStack() {
      -        stackPeek = null;
      -    }
      -
      -    /* スタックの長さを取得 */
      -    public int size() {
      -        return stkSize;
      -    }
      -
      -    /* スタックが空かどうかを判定 */
      -    public boolean isEmpty() {
      -        return size() == 0;
      -    }
      -
      -    /* プッシュ */
      -    public void push(int num) {
      -        ListNode node = new ListNode(num);
      -        node.next = stackPeek;
      -        stackPeek = node;
      -        stkSize++;
      +
      linkedlist_stack.cpp
      /* 連結リストベースのスタック */
      +class LinkedListStack {
      +  private:
      +    ListNode *stackTop; // 先頭ノードをスタックトップとする
      +    int stkSize;        // スタックの長さ
      +
      +  public:
      +    LinkedListStack() {
      +        stackTop = nullptr;
      +        stkSize = 0;
      +    }
      +
      +    ~LinkedListStack() {
      +        // 連結リストを走査してノードを削除し、メモリを解放する
      +        freeMemoryLinkedList(stackTop);
      +    }
      +
      +    /* スタックの長さを取得 */
      +    int size() {
      +        return stkSize;
      +    }
      +
      +    /* スタックが空かどうかを判定 */
      +    bool isEmpty() {
      +        return size() == 0;
           }
       
      -    /* ポップ */
      -    public int pop() {
      -        int num = peek();
      -        stackPeek = stackPeek.next;
      -        stkSize--;
      -        return num;
      +    /* プッシュ */
      +    void push(int num) {
      +        ListNode *node = new ListNode(num);
      +        node->next = stackTop;
      +        stackTop = node;
      +        stkSize++;
           }
       
      -    /* スタックトップ要素にアクセス */
      -    public int peek() {
      -        if (isEmpty())
      -            throw new IndexOutOfBoundsException();
      -        return stackPeek.val;
      -    }
      -
      -    /* List を Array に変換して返す */
      -    public int[] toArray() {
      -        ListNode node = stackPeek;
      -        int[] res = new int[size()];
      -        for (int i = res.length - 1; i >= 0; i--) {
      -            res[i] = node.val;
      -            node = node.next;
      -        }
      -        return res;
      +    /* ポップ */
      +    int pop() {
      +        int num = top();
      +        ListNode *tmp = stackTop;
      +        stackTop = stackTop->next;
      +        // メモリを解放する
      +        delete tmp;
      +        stkSize--;
      +        return num;
      +    }
      +
      +    /* スタックトップの要素にアクセス */
      +    int top() {
      +        if (isEmpty())
      +            throw out_of_range("スタックが空です");
      +        return stackTop->val;
           }
      -}
      +
      +    /* List を Array に変換して返す */
      +    vector<int> toVector() {
      +        ListNode *node = stackTop;
      +        vector<int> res(size());
      +        for (int i = res.size() - 1; i >= 0; i--) {
      +            res[i] = node->val;
      +            node = node->next;
      +        }
      +        return res;
      +    }
      +};
       
      -
      linkedlist_stack.cs
      [class]{LinkedListStack}-[func]{}
      +
      linkedlist_stack.java
      /* 連結リストベースのスタック */
      +class LinkedListStack {
      +    private ListNode stackPeek; // 先頭ノードをスタックトップとする
      +    private int stkSize = 0; // スタックの長さ
      +
      +    public LinkedListStack() {
      +        stackPeek = null;
      +    }
      +
      +    /* スタックの長さを取得 */
      +    public int size() {
      +        return stkSize;
      +    }
      +
      +    /* スタックが空かどうかを判定 */
      +    public boolean isEmpty() {
      +        return size() == 0;
      +    }
      +
      +    /* プッシュ */
      +    public void push(int num) {
      +        ListNode node = new ListNode(num);
      +        node.next = stackPeek;
      +        stackPeek = node;
      +        stkSize++;
      +    }
      +
      +    /* ポップ */
      +    public int pop() {
      +        int num = peek();
      +        stackPeek = stackPeek.next;
      +        stkSize--;
      +        return num;
      +    }
      +
      +    /* スタックトップの要素にアクセス */
      +    public int peek() {
      +        if (isEmpty())
      +            throw new IndexOutOfBoundsException();
      +        return stackPeek.val;
      +    }
      +
      +    /* List を Array に変換して返す */
      +    public int[] toArray() {
      +        ListNode node = stackPeek;
      +        int[] res = new int[size()];
      +        for (int i = res.length - 1; i >= 0; i--) {
      +            res[i] = node.val;
      +            node = node.next;
      +        }
      +        return res;
      +    }
      +}
       
      -
      linkedlist_stack.go
      [class]{linkedListStack}-[func]{}
      +
      linkedlist_stack.cs
      /* 連結リストベースのスタック */
      +class LinkedListStack {
      +    ListNode? stackPeek;  // 先頭ノードをスタックトップとする
      +    int stkSize = 0;   // スタックの長さ
      +
      +    public LinkedListStack() {
      +        stackPeek = null;
      +    }
      +
      +    /* スタックの長さを取得 */
      +    public int Size() {
      +        return stkSize;
      +    }
      +
      +    /* スタックが空かどうかを判定 */
      +    public bool IsEmpty() {
      +        return Size() == 0;
      +    }
      +
      +    /* プッシュ */
      +    public void Push(int num) {
      +        ListNode node = new(num) {
      +            next = stackPeek
      +        };
      +        stackPeek = node;
      +        stkSize++;
      +    }
      +
      +    /* ポップ */
      +    public int Pop() {
      +        int num = Peek();
      +        stackPeek = stackPeek!.next;
      +        stkSize--;
      +        return num;
      +    }
      +
      +    /* スタックトップの要素にアクセス */
      +    public int Peek() {
      +        if (IsEmpty())
      +            throw new Exception();
      +        return stackPeek!.val;
      +    }
      +
      +    /* List を Array に変換して返す */
      +    public int[] ToArray() {
      +        if (stackPeek == null)
      +            return [];
      +
      +        ListNode? node = stackPeek;
      +        int[] res = new int[Size()];
      +        for (int i = res.Length - 1; i >= 0; i--) {
      +            res[i] = node!.val;
      +            node = node.next;
      +        }
      +        return res;
      +    }
      +}
       
      -
      linkedlist_stack.swift
      [class]{LinkedListStack}-[func]{}
      +
      linkedlist_stack.go
      /* 連結リストベースのスタック */
      +type linkedListStack struct {
      +    // 組み込みパッケージ list でスタックを実装する
      +    data *list.List
      +}
      +
      +/* スタックを初期化 */
      +func newLinkedListStack() *linkedListStack {
      +    return &linkedListStack{
      +        data: list.New(),
      +    }
      +}
      +
      +/* プッシュ */
      +func (s *linkedListStack) push(value int) {
      +    s.data.PushBack(value)
      +}
      +
      +/* ポップ */
      +func (s *linkedListStack) pop() any {
      +    if s.isEmpty() {
      +        return nil
      +    }
      +    e := s.data.Back()
      +    s.data.Remove(e)
      +    return e.Value
      +}
      +
      +/* スタックトップの要素にアクセス */
      +func (s *linkedListStack) peek() any {
      +    if s.isEmpty() {
      +        return nil
      +    }
      +    e := s.data.Back()
      +    return e.Value
      +}
      +
      +/* スタックの長さを取得 */
      +func (s *linkedListStack) size() int {
      +    return s.data.Len()
      +}
      +
      +/* スタックが空かどうかを判定 */
      +func (s *linkedListStack) isEmpty() bool {
      +    return s.data.Len() == 0
      +}
      +
      +/* 表示用に List を取得 */
      +func (s *linkedListStack) toList() *list.List {
      +    return s.data
      +}
       
      -
      linkedlist_stack.js
      [class]{LinkedListStack}-[func]{}
      +
      linkedlist_stack.swift
      /* 連結リストベースのスタック */
      +class LinkedListStack {
      +    private var _peek: ListNode? // 先頭ノードをスタックトップとする
      +    private var _size: Int // スタックの長さ
      +
      +    init() {
      +        _size = 0
      +    }
      +
      +    /* スタックの長さを取得 */
      +    func size() -> Int {
      +        _size
      +    }
      +
      +    /* スタックが空かどうかを判定 */
      +    func isEmpty() -> Bool {
      +        size() == 0
      +    }
      +
      +    /* プッシュ */
      +    func push(num: Int) {
      +        let node = ListNode(x: num)
      +        node.next = _peek
      +        _peek = node
      +        _size += 1
      +    }
      +
      +    /* ポップ */
      +    @discardableResult
      +    func pop() -> Int {
      +        let num = peek()
      +        _peek = _peek?.next
      +        _size -= 1
      +        return num
      +    }
      +
      +    /* スタックトップの要素にアクセス */
      +    func peek() -> Int {
      +        if isEmpty() {
      +            fatalError("スタックが空です")
      +        }
      +        return _peek!.val
      +    }
      +
      +    /* List を Array に変換して返す */
      +    func toArray() -> [Int] {
      +        var node = _peek
      +        var res = Array(repeating: 0, count: size())
      +        for i in res.indices.reversed() {
      +            res[i] = node!.val
      +            node = node?.next
      +        }
      +        return res
      +    }
      +}
       
      -
      linkedlist_stack.ts
      [class]{LinkedListStack}-[func]{}
      +
      linkedlist_stack.js
      /* 連結リストベースのスタック */
      +class LinkedListStack {
      +    #stackPeek; // 先頭ノードをスタックトップとする
      +    #stkSize = 0; // スタックの長さ
      +
      +    constructor() {
      +        this.#stackPeek = null;
      +    }
      +
      +    /* スタックの長さを取得 */
      +    get size() {
      +        return this.#stkSize;
      +    }
      +
      +    /* スタックが空かどうかを判定 */
      +    isEmpty() {
      +        return this.size === 0;
      +    }
      +
      +    /* プッシュ */
      +    push(num) {
      +        const node = new ListNode(num);
      +        node.next = this.#stackPeek;
      +        this.#stackPeek = node;
      +        this.#stkSize++;
      +    }
      +
      +    /* ポップ */
      +    pop() {
      +        const num = this.peek();
      +        this.#stackPeek = this.#stackPeek.next;
      +        this.#stkSize--;
      +        return num;
      +    }
      +
      +    /* スタックトップの要素にアクセス */
      +    peek() {
      +        if (!this.#stackPeek) throw new Error('スタックが空');
      +        return this.#stackPeek.val;
      +    }
      +
      +    /* 連結リストを Array に変換して返す */
      +    toArray() {
      +        let node = this.#stackPeek;
      +        const res = new Array(this.size);
      +        for (let i = res.length - 1; i >= 0; i--) {
      +            res[i] = node.val;
      +            node = node.next;
      +        }
      +        return res;
      +    }
      +}
       
      -
      linkedlist_stack.dart
      [class]{LinkedListStack}-[func]{}
      +
      linkedlist_stack.ts
      /* 連結リストベースのスタック */
      +class LinkedListStack {
      +    private stackPeek: ListNode | null; // 先頭ノードをスタックトップとする
      +    private stkSize: number = 0; // スタックの長さ
      +
      +    constructor() {
      +        this.stackPeek = null;
      +    }
      +
      +    /* スタックの長さを取得 */
      +    get size(): number {
      +        return this.stkSize;
      +    }
      +
      +    /* スタックが空かどうかを判定 */
      +    isEmpty(): boolean {
      +        return this.size === 0;
      +    }
      +
      +    /* プッシュ */
      +    push(num: number): void {
      +        const node = new ListNode(num);
      +        node.next = this.stackPeek;
      +        this.stackPeek = node;
      +        this.stkSize++;
      +    }
      +
      +    /* ポップ */
      +    pop(): number {
      +        const num = this.peek();
      +        if (!this.stackPeek) throw new Error('スタックが空です');
      +        this.stackPeek = this.stackPeek.next;
      +        this.stkSize--;
      +        return num;
      +    }
      +
      +    /* スタックトップの要素にアクセス */
      +    peek(): number {
      +        if (!this.stackPeek) throw new Error('スタックが空です');
      +        return this.stackPeek.val;
      +    }
      +
      +    /* 連結リストを Array に変換して返す */
      +    toArray(): number[] {
      +        let node = this.stackPeek;
      +        const res = new Array<number>(this.size);
      +        for (let i = res.length - 1; i >= 0; i--) {
      +            res[i] = node!.val;
      +            node = node!.next;
      +        }
      +        return res;
      +    }
      +}
       
      -
      linkedlist_stack.rs
      [class]{LinkedListStack}-[func]{}
      +
      linkedlist_stack.dart
      /* 連結リストクラスに基づくスタック */
      +class LinkedListStack {
      +  ListNode? _stackPeek; // 先頭ノードをスタックトップとする
      +  int _stkSize = 0; // スタックの長さ
      +
      +  LinkedListStack() {
      +    _stackPeek = null;
      +  }
      +
      +  /* スタックの長さを取得 */
      +  int size() {
      +    return _stkSize;
      +  }
      +
      +  /* スタックが空かどうかを判定 */
      +  bool isEmpty() {
      +    return _stkSize == 0;
      +  }
      +
      +  /* プッシュ */
      +  void push(int _num) {
      +    final ListNode node = ListNode(_num);
      +    node.next = _stackPeek;
      +    _stackPeek = node;
      +    _stkSize++;
      +  }
      +
      +  /* ポップ */
      +  int pop() {
      +    final int _num = peek();
      +    _stackPeek = _stackPeek!.next;
      +    _stkSize--;
      +    return _num;
      +  }
      +
      +  /* スタックトップの要素にアクセス */
      +  int peek() {
      +    if (_stackPeek == null) {
      +      throw Exception("スタックが空です");
      +    }
      +    return _stackPeek!.val;
      +  }
      +
      +  /* 連結リストを List に変換して返す */
      +  List<int> toList() {
      +    ListNode? node = _stackPeek;
      +    List<int> list = [];
      +    while (node != null) {
      +      list.add(node.val);
      +      node = node.next;
      +    }
      +    list = list.reversed.toList();
      +    return list;
      +  }
      +}
       
      -
      linkedlist_stack.c
      [class]{LinkedListStack}-[func]{}
      +
      linkedlist_stack.rs
      /* 連結リストベースのスタック */
      +#[allow(dead_code)]
      +pub struct LinkedListStack<T> {
      +    stack_peek: Option<Rc<RefCell<ListNode<T>>>>, // 先頭ノードをスタックトップとする
      +    stk_size: usize,                              // スタックの長さ
      +}
      +
      +impl<T: Copy> LinkedListStack<T> {
      +    pub fn new() -> Self {
      +        Self {
      +            stack_peek: None,
      +            stk_size: 0,
      +        }
      +    }
      +
      +    /* スタックの長さを取得 */
      +    pub fn size(&self) -> usize {
      +        return self.stk_size;
      +    }
      +
      +    /* スタックが空かどうかを判定 */
      +    pub fn is_empty(&self) -> bool {
      +        return self.size() == 0;
      +    }
      +
      +    /* プッシュ */
      +    pub fn push(&mut self, num: T) {
      +        let node = ListNode::new(num);
      +        node.borrow_mut().next = self.stack_peek.take();
      +        self.stack_peek = Some(node);
      +        self.stk_size += 1;
      +    }
      +
      +    /* ポップ */
      +    pub fn pop(&mut self) -> Option<T> {
      +        self.stack_peek.take().map(|old_head| {
      +            self.stack_peek = old_head.borrow_mut().next.take();
      +            self.stk_size -= 1;
      +
      +            old_head.borrow().val
      +        })
      +    }
      +
      +    /* スタックトップの要素にアクセス */
      +    pub fn peek(&self) -> Option<&Rc<RefCell<ListNode<T>>>> {
      +        self.stack_peek.as_ref()
      +    }
      +
      +    /* List を Array に変換して返す */
      +    pub fn to_array(&self) -> Vec<T> {
      +        fn _to_array<T: Sized + Copy>(head: Option<&Rc<RefCell<ListNode<T>>>>) -> Vec<T> {
      +            if let Some(node) = head {
      +                let mut nums = _to_array(node.borrow().next.as_ref());
      +                nums.push(node.borrow().val);
      +                return nums;
      +            }
      +            return Vec::new();
      +        }
      +
      +        _to_array(self.peek())
      +    }
      +}
       
      -
      linkedlist_stack.kt
      [class]{LinkedListStack}-[func]{}
      +
      linkedlist_stack.c
      /* 連結リストベースのスタック */
      +typedef struct {
      +    ListNode *top; // 先頭ノードをスタックトップとする
      +    int size;      // スタックの長さ
      +} LinkedListStack;
      +
      +/* コンストラクタ */
      +LinkedListStack *newLinkedListStack() {
      +    LinkedListStack *s = malloc(sizeof(LinkedListStack));
      +    s->top = NULL;
      +    s->size = 0;
      +    return s;
      +}
      +
      +/* デストラクタ */
      +void delLinkedListStack(LinkedListStack *s) {
      +    while (s->top) {
      +        ListNode *n = s->top->next;
      +        free(s->top);
      +        s->top = n;
      +    }
      +    free(s);
      +}
      +
      +/* スタックの長さを取得 */
      +int size(LinkedListStack *s) {
      +    return s->size;
      +}
      +
      +/* スタックが空かどうかを判定 */
      +bool isEmpty(LinkedListStack *s) {
      +    return size(s) == 0;
      +}
      +
      +/* プッシュ */
      +void push(LinkedListStack *s, int num) {
      +    ListNode *node = (ListNode *)malloc(sizeof(ListNode));
      +    node->next = s->top; // 新しく追加したノードのポインタフィールドを更新
      +    node->val = num;     // 新しく追加したノードのデータフィールドを更新
      +    s->top = node;       // スタックトップを更新
      +    s->size++;           // スタックサイズを更新
      +}
      +
      +/* スタックトップの要素にアクセス */
      +int peek(LinkedListStack *s) {
      +    if (s->size == 0) {
      +        printf("スタックは空です\n");
      +        return INT_MAX;
      +    }
      +    return s->top->val;
      +}
      +
      +/* ポップ */
      +int pop(LinkedListStack *s) {
      +    int val = peek(s);
      +    ListNode *tmp = s->top;
      +    s->top = s->top->next;
      +    // メモリを解放する
      +    free(tmp);
      +    s->size--;
      +    return val;
      +}
       
      -
      linkedlist_stack.rb
      [class]{LinkedListStack}-[func]{}
      +
      linkedlist_stack.kt
      /* 連結リストベースのスタック */
      +class LinkedListStack(
      +    private var stackPeek: ListNode? = null, // 先頭ノードをスタックトップとする
      +    private var stkSize: Int = 0 // スタックの長さ
      +) {
      +
      +    /* スタックの長さを取得 */
      +    fun size(): Int {
      +        return stkSize
      +    }
      +
      +    /* スタックが空かどうかを判定 */
      +    fun isEmpty(): Boolean {
      +        return size() == 0
      +    }
      +
      +    /* プッシュ */
      +    fun push(num: Int) {
      +        val node = ListNode(num)
      +        node.next = stackPeek
      +        stackPeek = node
      +        stkSize++
      +    }
      +
      +    /* ポップ */
      +    fun pop(): Int? {
      +        val num = peek()
      +        stackPeek = stackPeek?.next
      +        stkSize--
      +        return num
      +    }
      +
      +    /* スタックトップの要素にアクセス */
      +    fun peek(): Int? {
      +        if (isEmpty()) throw IndexOutOfBoundsException()
      +        return stackPeek?._val
      +    }
      +
      +    /* List を Array に変換して返す */
      +    fun toArray(): IntArray {
      +        var node = stackPeek
      +        val res = IntArray(size())
      +        for (i in res.size - 1 downTo 0) {
      +            res[i] = node?._val!!
      +            node = node.next
      +        }
      +        return res
      +    }
      +}
      +
      +
      +
      +
      linkedlist_stack.rb
      ### 連結リストで実装したスタック ###
      +class LinkedListStack
      +  attr_reader :size
      +
      +  ### コンストラクタ ###
      +  def initialize
      +    @size = 0
      +  end
      +
      +  ### スタックが空か判定 ###
      +  def is_empty?
      +    @peek.nil?
      +  end
      +
      +  ### プッシュ ###
      +  def push(val)
      +    node = ListNode.new(val)
      +    node.next = @peek
      +    @peek = node
      +    @size += 1
      +  end
      +
      +  ### ポップ ###
      +  def pop
      +    num = peek
      +    @peek = @peek.next
      +    @size -= 1
      +    num
      +  end
      +
      +  ### スタックトップ要素を参照 ###
      +  def peek
      +    raise IndexError, 'スタックは空です' if is_empty?
      +
      +    @peek.val
      +  end
      +
      +  ### 連結リストを Array に変換して返す ###
      +  def to_array
      +    arr = []
      +    node = @peek
      +    while node
      +      arr << node.val
      +      node = node.next
      +    end
      +    arr.reverse
      +  end
      +end
       
      -

      2.   配列ベースの実装

      -

      配列を使用してスタックを実装する場合、配列の末尾をスタックのトップと考えることができます。下図に示すように、プッシュとポップ操作は、それぞれ配列の末尾での要素の追加と削除に対応し、どちらも時間計算量\(O(1)\)です。

      +
      +コードの可視化 +

      +

      +
      +

      2.   配列による実装

      +

      配列でスタックを実装する場合、配列の末尾をスタックトップとして扱えます。下図のように、プッシュとポップはそれぞれ配列末尾への要素追加と削除に対応し、どちらの時間計算量も \(O(1)\) です。

      -

      配列によるスタック実装のプッシュとポップ操作

      +

      配列によるスタック実装のプッシュ・ポップ操作

      array_stack_push

      @@ -5006,198 +5594,631 @@
      -

      図 5-3   配列によるスタック実装のプッシュとポップ操作

      +

      図 5-3   配列によるスタック実装のプッシュ・ポップ操作

      -

      スタックにプッシュされる要素が継続的に増加する可能性があるため、動的配列を使用でき、配列拡張を自分で処理する必要がありません。以下はサンプルコードです:

      +

      プッシュされる要素は際限なく増える可能性があるため、動的配列を使えば、配列の拡張を自前で処理する必要がありません。以下にコード例を示します:

      -
      array_stack.py
      class ArrayStack:
      -    """配列ベースのスタッククラス"""
      -
      -    def __init__(self):
      -        """コンストラクタ"""
      -        self._stack: list[int] = []
      -
      -    def size(self) -> int:
      -        """スタックの長さを取得"""
      -        return len(self._stack)
      -
      -    def is_empty(self) -> bool:
      -        """スタックが空かどうかを判定"""
      -        return self.size() == 0
      -
      -    def push(self, item: int):
      -        """プッシュ"""
      -        self._stack.append(item)
      -
      -    def pop(self) -> int:
      -        """ポップ"""
      -        if self.is_empty():
      -            raise IndexError("Stack is empty")
      -        return self._stack.pop()
      -
      -    def peek(self) -> int:
      -        """スタックトップ要素にアクセス"""
      -        if self.is_empty():
      -            raise IndexError("Stack is empty")
      -        return self._stack[-1]
      -
      -    def to_list(self) -> list[int]:
      -        """出力用の配列を返す"""
      -        return self._stack
      -
      -
      -
      -
      array_stack.cpp
      /* 配列に基づくスタッククラス */
      -class ArrayStack {
      -  private:
      -    vector<int> stack;
      -
      -  public:
      -    /* スタックの長さを取得 */
      -    int size() {
      -        return stack.size();
      -    }
      +
      array_stack.py
      class ArrayStack:
      +    """配列ベースのスタック"""
      +
      +    def __init__(self):
      +        """コンストラクタ"""
      +        self._stack: list[int] = []
      +
      +    def size(self) -> int:
      +        """スタックの長さを取得"""
      +        return len(self._stack)
       
      -    /* スタックが空かどうかを判定 */
      -    bool isEmpty() {
      -        return stack.size() == 0;
      -    }
      -
      -    /* プッシュ */
      -    void push(int num) {
      -        stack.push_back(num);
      -    }
      -
      -    /* ポップ */
      -    int pop() {
      -        int num = top();
      -        stack.pop_back();
      -        return num;
      -    }
      -
      -    /* スタックトップ要素にアクセス */
      -    int top() {
      -        if (isEmpty())
      -            throw out_of_range("Stack is empty");
      -        return stack.back();
      -    }
      -
      -    /* Vectorを返却 */
      -    vector<int> toVector() {
      -        return stack;
      -    }
      -};
      +    def is_empty(self) -> bool:
      +        """スタックが空かどうかを判定"""
      +        return self.size() == 0
      +
      +    def push(self, item: int):
      +        """プッシュ"""
      +        self._stack.append(item)
      +
      +    def pop(self) -> int:
      +        """ポップ"""
      +        if self.is_empty():
      +            raise IndexError("スタックが空です")
      +        return self._stack.pop()
      +
      +    def peek(self) -> int:
      +        """スタックトップの要素にアクセス"""
      +        if self.is_empty():
      +            raise IndexError("スタックが空です")
      +        return self._stack[-1]
      +
      +    def to_list(self) -> list[int]:
      +        """表示用のリストを返す"""
      +        return self._stack
       
      -
      array_stack.java
      /* 配列に基づくスタッククラス */
      -class ArrayStack {
      -    private ArrayList<Integer> stack;
      -
      -    public ArrayStack() {
      -        // リスト(動的配列)を初期化
      -        stack = new ArrayList<>();
      -    }
      -
      -    /* スタックの長さを取得 */
      -    public int size() {
      -        return stack.size();
      -    }
      -
      -    /* スタックが空かどうかを判定 */
      -    public boolean isEmpty() {
      -        return size() == 0;
      -    }
      -
      -    /* プッシュ */
      -    public void push(int num) {
      -        stack.add(num);
      -    }
      -
      -    /* ポップ */
      -    public int pop() {
      -        if (isEmpty())
      -            throw new IndexOutOfBoundsException();
      -        return stack.remove(size() - 1);
      -    }
      -
      -    /* スタックトップ要素にアクセス */
      -    public int peek() {
      -        if (isEmpty())
      -            throw new IndexOutOfBoundsException();
      -        return stack.get(size() - 1);
      -    }
      -
      -    /* List を Array に変換して返す */
      -    public Object[] toArray() {
      -        return stack.toArray();
      -    }
      -}
      +
      array_stack.cpp
      /* 配列ベースのスタック */
      +class ArrayStack {
      +  private:
      +    vector<int> stack;
      +
      +  public:
      +    /* スタックの長さを取得 */
      +    int size() {
      +        return stack.size();
      +    }
      +
      +    /* スタックが空かどうかを判定 */
      +    bool isEmpty() {
      +        return stack.size() == 0;
      +    }
      +
      +    /* プッシュ */
      +    void push(int num) {
      +        stack.push_back(num);
      +    }
      +
      +    /* ポップ */
      +    int pop() {
      +        int num = top();
      +        stack.pop_back();
      +        return num;
      +    }
      +
      +    /* スタックトップの要素にアクセス */
      +    int top() {
      +        if (isEmpty())
      +            throw out_of_range("スタックが空です");
      +        return stack.back();
      +    }
      +
      +    /* Vector を返す */
      +    vector<int> toVector() {
      +        return stack;
      +    }
      +};
       
      -
      array_stack.cs
      [class]{ArrayStack}-[func]{}
      +
      array_stack.java
      /* 配列ベースのスタック */
      +class ArrayStack {
      +    private ArrayList<Integer> stack;
      +
      +    public ArrayStack() {
      +        // リスト(動的配列)を初期化する
      +        stack = new ArrayList<>();
      +    }
      +
      +    /* スタックの長さを取得 */
      +    public int size() {
      +        return stack.size();
      +    }
      +
      +    /* スタックが空かどうかを判定 */
      +    public boolean isEmpty() {
      +        return size() == 0;
      +    }
      +
      +    /* プッシュ */
      +    public void push(int num) {
      +        stack.add(num);
      +    }
      +
      +    /* ポップ */
      +    public int pop() {
      +        if (isEmpty())
      +            throw new IndexOutOfBoundsException();
      +        return stack.remove(size() - 1);
      +    }
      +
      +    /* スタックトップの要素にアクセス */
      +    public int peek() {
      +        if (isEmpty())
      +            throw new IndexOutOfBoundsException();
      +        return stack.get(size() - 1);
      +    }
      +
      +    /* List を Array に変換して返す */
      +    public Object[] toArray() {
      +        return stack.toArray();
      +    }
      +}
       
      -
      array_stack.go
      [class]{arrayStack}-[func]{}
      +
      array_stack.cs
      /* 配列ベースのスタック */
      +class ArrayStack {
      +    List<int> stack;
      +    public ArrayStack() {
      +        // リスト(動的配列)を初期化する
      +        stack = [];
      +    }
      +
      +    /* スタックの長さを取得 */
      +    public int Size() {
      +        return stack.Count;
      +    }
      +
      +    /* スタックが空かどうかを判定 */
      +    public bool IsEmpty() {
      +        return Size() == 0;
      +    }
      +
      +    /* プッシュ */
      +    public void Push(int num) {
      +        stack.Add(num);
      +    }
      +
      +    /* ポップ */
      +    public int Pop() {
      +        if (IsEmpty())
      +            throw new Exception();
      +        var val = Peek();
      +        stack.RemoveAt(Size() - 1);
      +        return val;
      +    }
      +
      +    /* スタックトップの要素にアクセス */
      +    public int Peek() {
      +        if (IsEmpty())
      +            throw new Exception();
      +        return stack[Size() - 1];
      +    }
      +
      +    /* List を Array に変換して返す */
      +    public int[] ToArray() {
      +        return [.. stack];
      +    }
      +}
       
      -
      array_stack.swift
      [class]{ArrayStack}-[func]{}
      +
      array_stack.go
      /* 配列ベースのスタック */
      +type arrayStack struct {
      +    data []int // データ
      +}
      +
      +/* スタックを初期化 */
      +func newArrayStack() *arrayStack {
      +    return &arrayStack{
      +        // スタックの長さを 0、容量を 16 に設定
      +        data: make([]int, 0, 16),
      +    }
      +}
      +
      +/* スタックの長さ */
      +func (s *arrayStack) size() int {
      +    return len(s.data)
      +}
      +
      +/* スタックが空かどうか */
      +func (s *arrayStack) isEmpty() bool {
      +    return s.size() == 0
      +}
      +
      +/* プッシュ */
      +func (s *arrayStack) push(v int) {
      +    // スライスは自動で拡張される
      +    s.data = append(s.data, v)
      +}
      +
      +/* ポップ */
      +func (s *arrayStack) pop() any {
      +    val := s.peek()
      +    s.data = s.data[:len(s.data)-1]
      +    return val
      +}
      +
      +/* スタックトップ要素を取得する */
      +func (s *arrayStack) peek() any {
      +    if s.isEmpty() {
      +        return nil
      +    }
      +    val := s.data[len(s.data)-1]
      +    return val
      +}
      +
      +/* 表示用に Slice を取得 */
      +func (s *arrayStack) toSlice() []int {
      +    return s.data
      +}
       
      -
      array_stack.js
      [class]{ArrayStack}-[func]{}
      +
      array_stack.swift
      /* 配列ベースのスタック */
      +class ArrayStack {
      +    private var stack: [Int]
      +
      +    init() {
      +        // リスト(動的配列)を初期化する
      +        stack = []
      +    }
      +
      +    /* スタックの長さを取得 */
      +    func size() -> Int {
      +        stack.count
      +    }
      +
      +    /* スタックが空かどうかを判定 */
      +    func isEmpty() -> Bool {
      +        stack.isEmpty
      +    }
      +
      +    /* プッシュ */
      +    func push(num: Int) {
      +        stack.append(num)
      +    }
      +
      +    /* ポップ */
      +    @discardableResult
      +    func pop() -> Int {
      +        if isEmpty() {
      +            fatalError("スタックが空です")
      +        }
      +        return stack.removeLast()
      +    }
      +
      +    /* スタックトップの要素にアクセス */
      +    func peek() -> Int {
      +        if isEmpty() {
      +            fatalError("スタックが空です")
      +        }
      +        return stack.last!
      +    }
      +
      +    /* List を Array に変換して返す */
      +    func toArray() -> [Int] {
      +        stack
      +    }
      +}
       
      -
      array_stack.ts
      [class]{ArrayStack}-[func]{}
      +
      array_stack.js
      /* 配列ベースのスタック */
      +class ArrayStack {
      +    #stack;
      +    constructor() {
      +        this.#stack = [];
      +    }
      +
      +    /* スタックの長さを取得 */
      +    get size() {
      +        return this.#stack.length;
      +    }
      +
      +    /* スタックが空かどうかを判定 */
      +    isEmpty() {
      +        return this.#stack.length === 0;
      +    }
      +
      +    /* プッシュ */
      +    push(num) {
      +        this.#stack.push(num);
      +    }
      +
      +    /* ポップ */
      +    pop() {
      +        if (this.isEmpty()) throw new Error('スタックが空');
      +        return this.#stack.pop();
      +    }
      +
      +    /* スタックトップの要素にアクセス */
      +    top() {
      +        if (this.isEmpty()) throw new Error('スタックが空');
      +        return this.#stack[this.#stack.length - 1];
      +    }
      +
      +    /* Array を返す */
      +    toArray() {
      +        return this.#stack;
      +    }
      +}
       
      -
      array_stack.dart
      [class]{ArrayStack}-[func]{}
      +
      array_stack.ts
      /* 配列ベースのスタック */
      +class ArrayStack {
      +    private stack: number[];
      +    constructor() {
      +        this.stack = [];
      +    }
      +
      +    /* スタックの長さを取得 */
      +    get size(): number {
      +        return this.stack.length;
      +    }
      +
      +    /* スタックが空かどうかを判定 */
      +    isEmpty(): boolean {
      +        return this.stack.length === 0;
      +    }
      +
      +    /* プッシュ */
      +    push(num: number): void {
      +        this.stack.push(num);
      +    }
      +
      +    /* ポップ */
      +    pop(): number | undefined {
      +        if (this.isEmpty()) throw new Error('スタックが空です');
      +        return this.stack.pop();
      +    }
      +
      +    /* スタックトップの要素にアクセス */
      +    top(): number | undefined {
      +        if (this.isEmpty()) throw new Error('スタックが空です');
      +        return this.stack[this.stack.length - 1];
      +    }
      +
      +    /* Array を返す */
      +    toArray() {
      +        return this.stack;
      +    }
      +}
       
      -
      array_stack.rs
      [class]{ArrayStack}-[func]{}
      +
      array_stack.dart
      /* 配列ベースのスタック */
      +class ArrayStack {
      +  late List<int> _stack;
      +  ArrayStack() {
      +    _stack = [];
      +  }
      +
      +  /* スタックの長さを取得 */
      +  int size() {
      +    return _stack.length;
      +  }
      +
      +  /* スタックが空かどうかを判定 */
      +  bool isEmpty() {
      +    return _stack.isEmpty;
      +  }
      +
      +  /* プッシュ */
      +  void push(int _num) {
      +    _stack.add(_num);
      +  }
      +
      +  /* ポップ */
      +  int pop() {
      +    if (isEmpty()) {
      +      throw Exception("スタックが空です");
      +    }
      +    return _stack.removeLast();
      +  }
      +
      +  /* スタックトップの要素にアクセス */
      +  int peek() {
      +    if (isEmpty()) {
      +      throw Exception("スタックが空です");
      +    }
      +    return _stack.last;
      +  }
      +
      +  /* スタックを Array に変換して返す */
      +  List<int> toArray() => _stack;
      +}
       
      -
      array_stack.c
      [class]{ArrayStack}-[func]{}
      +
      array_stack.rs
      /* 配列ベースのスタック */
      +struct ArrayStack<T> {
      +    stack: Vec<T>,
      +}
      +
      +impl<T> ArrayStack<T> {
      +    /* スタックを初期化 */
      +    fn new() -> ArrayStack<T> {
      +        ArrayStack::<T> {
      +            stack: Vec::<T>::new(),
      +        }
      +    }
      +
      +    /* スタックの長さを取得 */
      +    fn size(&self) -> usize {
      +        self.stack.len()
      +    }
      +
      +    /* スタックが空かどうかを判定 */
      +    fn is_empty(&self) -> bool {
      +        self.size() == 0
      +    }
      +
      +    /* プッシュ */
      +    fn push(&mut self, num: T) {
      +        self.stack.push(num);
      +    }
      +
      +    /* ポップ */
      +    fn pop(&mut self) -> Option<T> {
      +        self.stack.pop()
      +    }
      +
      +    /* スタックトップの要素にアクセス */
      +    fn peek(&self) -> Option<&T> {
      +        if self.is_empty() {
      +            panic!("スタックが空です")
      +        };
      +        self.stack.last()
      +    }
      +
      +    /* &Vec を返す */
      +    fn to_array(&self) -> &Vec<T> {
      +        &self.stack
      +    }
      +}
       
      -
      array_stack.kt
      [class]{ArrayStack}-[func]{}
      +
      array_stack.c
      /* 配列ベースのスタック */
      +typedef struct {
      +    int *data;
      +    int size;
      +} ArrayStack;
      +
      +/* コンストラクタ */
      +ArrayStack *newArrayStack() {
      +    ArrayStack *stack = malloc(sizeof(ArrayStack));
      +    // 大きめの容量で初期化し、拡張を避ける
      +    stack->data = malloc(sizeof(int) * MAX_SIZE);
      +    stack->size = 0;
      +    return stack;
      +}
      +
      +/* デストラクタ */
      +void delArrayStack(ArrayStack *stack) {
      +    free(stack->data);
      +    free(stack);
      +}
      +
      +/* スタックの長さを取得 */
      +int size(ArrayStack *stack) {
      +    return stack->size;
      +}
      +
      +/* スタックが空かどうかを判定 */
      +bool isEmpty(ArrayStack *stack) {
      +    return stack->size == 0;
      +}
      +
      +/* プッシュ */
      +void push(ArrayStack *stack, int num) {
      +    if (stack->size == MAX_SIZE) {
      +        printf("スタックは満杯です\n");
      +        return;
      +    }
      +    stack->data[stack->size] = num;
      +    stack->size++;
      +}
      +
      +/* スタックトップの要素にアクセス */
      +int peek(ArrayStack *stack) {
      +    if (stack->size == 0) {
      +        printf("スタックは空です\n");
      +        return INT_MAX;
      +    }
      +    return stack->data[stack->size - 1];
      +}
      +
      +/* ポップ */
      +int pop(ArrayStack *stack) {
      +    int val = peek(stack);
      +    stack->size--;
      +    return val;
      +}
       
      -
      array_stack.rb
      [class]{ArrayStack}-[func]{}
      +
      array_stack.kt
      /* 配列ベースのスタック */
      +class ArrayStack {
      +    // リスト(動的配列)を初期化する
      +    private val stack = mutableListOf<Int>()
      +
      +    /* スタックの長さを取得 */
      +    fun size(): Int {
      +        return stack.size
      +    }
      +
      +    /* スタックが空かどうかを判定 */
      +    fun isEmpty(): Boolean {
      +        return size() == 0
      +    }
      +
      +    /* プッシュ */
      +    fun push(num: Int) {
      +        stack.add(num)
      +    }
      +
      +    /* ポップ */
      +    fun pop(): Int {
      +        if (isEmpty()) throw IndexOutOfBoundsException()
      +        return stack.removeAt(size() - 1)
      +    }
      +
      +    /* スタックトップの要素にアクセス */
      +    fun peek(): Int {
      +        if (isEmpty()) throw IndexOutOfBoundsException()
      +        return stack[size() - 1]
      +    }
      +
      +    /* List を Array に変換して返す */
      +    fun toArray(): Array<Any> {
      +        return stack.toTypedArray()
      +    }
      +}
      +
      +
      +
      +
      array_stack.rb
      ### 配列で実装したスタック ###
      +class ArrayStack
      +  ### コンストラクタ ###
      +  def initialize
      +    @stack = []
      +  end
      +
      +  ### スタックの長さを取得 ###
      +  def size
      +    @stack.length
      +  end
      +
      +  ### スタックが空か判定 ###
      +  def is_empty?
      +    @stack.empty?
      +  end
      +
      +  ### プッシュ ###
      +  def push(item)
      +    @stack << item
      +  end
      +
      +  ### ポップ ###
      +  def pop
      +    raise IndexError, 'スタックは空です' if is_empty?
      +
      +    @stack.pop
      +  end
      +
      +  ### スタックトップ要素を参照 ###
      +  def peek
      +    raise IndexError, 'スタックは空です' if is_empty?
      +
      +    @stack.last
      +  end
      +
      +  ### 表示用のリストを返す ###
      +  def to_array
      +    @stack
      +  end
      +end
       
      +
      +コードの可視化 +

      +

      +

      5.1.3   2つの実装の比較

      -

      サポートされる操作

      -

      両方の実装は、スタックで定義されたすべての操作をサポートします。配列実装はさらにランダムアクセスをサポートしますが、これはスタック定義の範囲を超えており、一般的には使用されません。

      +

      対応する操作

      +

      どちらの実装も、スタックの定義に含まれる各種操作をサポートします。配列ベースの実装はランダムアクセスも可能ですが、これはスタックの定義範囲を超えているため、通常は利用しません。

      時間効率

      -

      配列ベースの実装では、プッシュとポップ操作の両方が事前に割り当てられた連続メモリで発生し、良好なキャッシュ局所性があるため効率が高くなります。しかし、プッシュ操作が配列容量を超える場合、リサイズメカニズムがトリガーされ、そのプッシュ操作の時間計算量は\(O(n)\)になります。

      -

      連結リスト実装では、リスト拡張は非常に柔軟で、配列拡張のような効率低下の問題はありません。しかし、プッシュ操作にはノードオブジェクトの初期化とポインタの変更が必要なため、効率は比較的低くなります。プッシュされる要素がすでにノードオブジェクトの場合、初期化ステップをスキップでき、効率が向上します。

      -

      したがって、プッシュとポップ操作の要素がintdoubleなどの基本データ型の場合、以下の結論を導くことができます:

      +

      配列ベースの実装では、プッシュとポップの両方があらかじめ確保された連続メモリ上で行われるため、キャッシュ局所性が高く、効率に優れます。ただし、プッシュ時に配列容量を超えると拡張処理が発生し、その1回のプッシュの時間計算量は \(O(n)\) になります。

      +

      連結リストベースの実装では、サイズ拡張が非常に柔軟であり、前述のような配列拡張による効率低下はありません。ただし、プッシュ時にはノードオブジェクトの初期化とポインタの更新が必要になるため、効率は相対的に低くなります。もっとも、プッシュする要素自体がノードオブジェクトであれば、初期化の手間を省けるため、効率を高められます。

      +

      以上を踏まえると、プッシュおよびポップの対象が intdouble のような基本データ型である場合、次の結論が得られます。

        -
      • 配列ベースのスタック実装は拡張時に効率が低下しますが、拡張は低頻度操作であるため、平均効率は高くなります。
      • -
      • 連結リストベースのスタック実装はより安定した効率パフォーマンスを提供します。
      • +
      • 配列ベースのスタックは拡張時に効率が低下しますが、拡張は低頻度の操作であるため、平均効率はより高くなります。
      • +
      • 連結リストベースのスタックは、より安定した効率を提供できます。

      空間効率

      -

      リストを初期化する際、システムは「初期容量」を割り当てますが、これは実際の必要量を超える可能性があります。さらに、拡張メカニズムは通常、特定の係数(2倍など)で容量を増加させ、これも実際の必要量を超える可能性があります。したがって、配列ベースのスタックは一部の空間を無駄にする可能性があります

      -

      しかし、連結リストノードはポインタを格納するための追加空間が必要なため、連結リストノードが占有する空間は比較的大きくなります

      -

      まとめると、どちらの実装がよりメモリ効率的かを単純に判断することはできません。特定の状況に基づく分析が必要です。

      +

      リストを初期化するとき、システムは「初期容量」を割り当てますが、この容量は実際の必要量を上回ることがあります。また、拡張は通常、一定の倍率(たとえば2倍)で行われるため、拡張後の容量も実際の必要量を超える可能性があります。したがって、配列ベースのスタックは一定のメモリ浪費を招く可能性があります。

      +

      一方で、連結リストのノードはポインタを追加で保持する必要があるため、連結リストノードは相対的に大きな領域を占有します。

      +

      以上より、どちらの実装がより省メモリかを単純に断定することはできず、具体的な状況に応じて分析する必要があります。

      5.1.4   スタックの典型的な応用

        -
      • ブラウザの戻ると進む、ソフトウェアの元に戻すとやり直し。新しいWebページを開くたびに、ブラウザは前のページをスタックにプッシュし、戻る操作(本質的にはポップ操作)を通じて前のページに戻ることができます。戻ると進むの両方をサポートするには、2つのスタックが連携して動作する必要があります。
      • -
      • プログラムのメモリ管理。関数が呼び出されるたびに、システムはスタックのトップにスタックフレームを追加して関数のコンテキスト情報を記録します。再帰関数では、下方向の再帰フェーズはスタックへのプッシュを続け、上方向のバックトラッキングフェーズはスタックからのポップを続けます。
      • +
      • ブラウザにおける戻ると進む、ソフトウェアにおける取り消しとやり直し。新しいWebページを開くたびに、ブラウザは直前のページをスタックにプッシュするため、戻る操作によって前のページに戻れます。戻る操作は実際にはポップに相当します。戻ると進むを同時にサポートするには、2つのスタックを組み合わせて実現する必要があります。
      • +
      • プログラムのメモリ管理。関数を呼び出すたびに、システムはスタックトップにスタックフレームを追加し、関数のコンテキスト情報を記録します。再帰関数では、下向きに再帰していく段階でプッシュが繰り返され、上向きにバックトラックする段階でポップが繰り返されます。
      diff --git a/ja/chapter_stack_and_queue/summary/index.html b/ja/chapter_stack_and_queue/summary/index.html index c3a8c9d02..d890afd34 100644 --- a/ja/chapter_stack_and_queue/summary/index.html +++ b/ja/chapter_stack_and_queue/summary/index.html @@ -6,7 +6,7 @@ - + @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1587,7 +1587,7 @@ - 1.   重要なポイント + 1.   要点の振り返り @@ -1663,7 +1663,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1685,7 +1685,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1707,7 +1707,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -2014,7 +2014,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2177,7 +2177,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2563,7 +2563,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2619,7 +2619,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2647,7 +2647,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -3185,7 +3185,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3241,7 +3241,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3269,7 +3269,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3462,7 +3462,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3490,7 +3490,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3631,7 +3631,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3659,7 +3659,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3687,7 +3687,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3715,7 +3715,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3908,7 +3908,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4153,7 +4153,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4299,7 +4299,7 @@ - 1.   重要なポイント + 1.   要点の振り返り @@ -4355,27 +4355,27 @@

      5.4   まとめ

      -

      1.   重要なポイント

      +

      1.   要点の振り返り

        -
      • スタックは後入れ先出し(LIFO)の原則に従うデータ構造で、配列または連結リストを使って実装できます。
      • -
      • 時間効率の観点では、スタックの配列実装の方が平均的な効率が高いです。ただし、拡張時には単一のプッシュ操作の時間計算量が\(O(n)\)に悪化する可能性があります。対照的に、スタックの連結リスト実装はより安定した効率を提供します。
      • -
      • 空間効率に関しては、スタックの配列実装は一定程度の空間の無駄につながる可能性があります。ただし、連結リストのノードが占有するメモリ空間は一般的に配列の要素よりも大きいことに注意することが重要です。
      • -
      • キューは先入れ先出し(FIFO)の原則に従うデータ構造で、同様に配列または連結リストを使って実装できます。キューの時間と空間効率に関する結論は、スタックと似ています。
      • -
      • 両端キュー(deque)はより柔軟なキューの種類で、両端での要素の追加と削除を可能にします。
      • +
      • スタックは後入れ先出しの原則に従うデータ構造であり、配列または連結リストで実装できます。
      • +
      • 時間効率の面では、スタックの配列実装は平均効率が高い一方、拡張時には 1 回のプッシュ操作の時間計算量が \(O(n)\) まで悪化します。これに対して、スタックの連結リスト実装はより安定した効率を示します。
      • +
      • 空間効率の面では、スタックの配列実装はある程度の領域の無駄を生む可能性があります。ただし、連結リストのノードが占有するメモリは配列要素よりも大きい点に注意が必要です。
      • +
      • キューは先入れ先出しの原則に従うデータ構造であり、同様に配列または連結リストで実装できます。時間効率と空間効率の比較における結論は、前述のスタックの場合と似ています。
      • +
      • 両端キューはより高い自由度を持つキューであり、両端で要素の追加と削除を行えます。

      2.   Q & A

      -

      Q: ブラウザの進む・戻る機能は双方向連結リストで実装されているのですか?

      -

      ブラウザの進む・戻るナビゲーションは本質的に「スタック」概念の現れです。ユーザーが新しいページを訪問すると、そのページがスタックの先頭に追加されます。戻るボタンをクリックすると、ページがスタックの先頭からポップされます。両端キュー(deque)は、「両端キュー」の章で述べたように、いくつかの追加操作を便利に実装できます。

      -

      Q: スタックからポップした後、ポップされたノードのメモリを解放する必要がありますか?

      -

      ポップされたノードが後で使用される場合は、そのメモリを解放する必要はありません。自動ガベージコレクションを持つJavaやPythonなどの言語では、手動のメモリ解放は必要ありません。CやC++では、手動のメモリ解放が必要です。

      -

      Q: 両端キューは2つのスタックを結合したもののように見えます。その用途は何ですか?

      -

      両端キューは、スタックとキューの組み合わせまたは2つのスタックを結合したもので、スタックとキューの両方のロジックを示します。したがって、スタックとキューのすべてのアプリケーションを実装でき、より大きな柔軟性を提供します。

      -

      Q: 元に戻すとやり直しは具体的にどのように実装されるのですか?

      -

      元に戻すとやり直しの操作は2つのスタックを使って実装されます:元に戻す用のスタックAとやり直し用のスタックBです。

      +

      Q:ブラウザの進む・戻るは双方向連結リストで実装されているのですか?

      +

      ブラウザの進む・戻る機能の本質は「スタック」の表れです。ユーザーが新しいページにアクセスすると、そのページはスタックの先頭に追加されます。ユーザーが戻るボタンをクリックすると、そのページはスタックの先頭から取り出されます。両端キューを使うといくつかの追加操作を簡単に実装でき、この点は「両端キュー」の章で触れています。

      +

      Q:ポップした後、そのノードのメモリを解放する必要はありますか?

      +

      後で取り出したノードを引き続き使うのであれば、メモリを解放する必要はありません。以降そのノードを使わない場合でも、JavaPython などの言語には自動ガベージコレクション機構があるため、手動でメモリを解放する必要はありません。一方、CC++ では手動でメモリを解放する必要があります。

      +

      Q:両端キューは 2 つのスタックをつなげたように見えますが、用途は何ですか?

      +

      両端キューは、スタックとキューの組み合わせ、あるいは 2 つのスタックをつなげたもののような構造です。表しているのはスタック + キューのロジックなので、スタックとキューのすべての応用を実現でき、しかもより柔軟です。

      +

      Q:取り消し(undo)とやり直し(redo)は具体的にどのように実装されますか?

      +

      2 つのスタックを使い、スタック A を取り消し用、スタック B をやり直し用に使います。

        -
      1. ユーザーが操作を実行するたびに、それがスタックAにプッシュされ、スタックBがクリアされます。
      2. -
      3. ユーザーが「元に戻す」を実行すると、最新の操作がスタックAからポップされ、スタックBにプッシュされます。
      4. -
      5. ユーザーが「やり直し」を実行すると、最新の操作がスタックBからポップされ、スタックAに戻されます。
      6. +
      7. ユーザーが操作を 1 つ実行するたびに、その操作をスタック A にプッシュし、スタック B を空にします。
      8. +
      9. ユーザーが「取り消し」を実行したときは、スタック A から直近の操作をポップし、それをスタック B にプッシュします。
      10. +
      11. ユーザーが「やり直し」を実行したときは、スタック B から直近の操作をポップし、それをスタック A にプッシュします。
      @@ -4423,7 +4423,7 @@ aria-label="フッター"
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1591,7 +1591,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1613,7 +1613,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1635,7 +1635,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1945,7 +1945,7 @@ - 7.3.1   完全二分木の表現 + 7.3.1   充足二分木を表現する @@ -1956,7 +1956,7 @@ - 7.3.2   任意の二分木の表現 + 7.3.2   任意の二分木を表現する @@ -1967,7 +1967,7 @@ - 7.3.3   利点と制限 + 7.3.3   利点と制約 @@ -2025,7 +2025,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2188,7 +2188,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2574,7 +2574,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2630,7 +2630,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2658,7 +2658,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -3196,7 +3196,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3252,7 +3252,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3280,7 +3280,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3473,7 +3473,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3501,7 +3501,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3642,7 +3642,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3670,7 +3670,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3698,7 +3698,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3726,7 +3726,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3919,7 +3919,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4164,7 +4164,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4310,7 +4310,7 @@ - 7.3.1   完全二分木の表現 + 7.3.1   充足二分木を表現する @@ -4321,7 +4321,7 @@ - 7.3.2   任意の二分木の表現 + 7.3.2   任意の二分木を表現する @@ -4332,7 +4332,7 @@ - 7.3.3   利点と制限 + 7.3.3   利点と制約 @@ -4377,162 +4377,164 @@

      7.3   二分木の配列表現

      -

      連結リスト表現では、二分木の格納単位はノードTreeNodeであり、ノードはポインタによって接続されます。連結リスト表現での二分木の基本操作については前の節で紹介しました。

      -

      では、配列を使って二分木を表現することはできるでしょうか?答えはイエスです。

      -

      7.3.1   完全二分木の表現

      -

      まず簡単なケースから分析してみましょう。完全二分木が与えられたとき、レベル順探索の順序に従ってすべてのノードを配列に格納し、各ノードは一意の配列インデックスに対応します。

      -

      レベル順探索の特性に基づいて、親ノードのインデックスとその子ノードの間の「マッピング公式」を導き出すことができます:ノードのインデックスが\(i\)の場合、その左の子のインデックスは\(2i + 1\)、右の子のインデックスは\(2i + 2\)です。下図は、さまざまなノードのインデックス間のマッピング関係を示しています。

      -

      完全二分木の配列表現

      -

      図 7-12   完全二分木の配列表現

      +

      連結リスト表現では、二分木の記憶単位はノード TreeNode であり、ノード同士はポインタによって接続されます。前節では、連結リスト表現における二分木の各種基本操作を紹介しました。

      +

      では、配列で二分木を表現できるでしょうか?答えはもちろん可能です。

      +

      7.3.1   充足二分木を表現する

      +

      まずは簡単な例を考えます。与えられた 1 本の充足二分木について、すべてのノードをレベル順走査の順に配列へ格納すると、各ノードは一意な配列インデックスに対応します。

      +

      レベル順走査の性質に基づくと、親ノードのインデックスと子ノードのインデックスの間にある「対応式」を導けます。あるノードのインデックスが \(i\) なら、その左子ノードのインデックスは \(2i + 1\) 、右子ノードのインデックスは \(2i + 2\) です。以下の図は、各ノードインデックス間の対応関係を示しています。

      +

      充足二分木の配列表現

      +

      図 7-12   充足二分木の配列表現

      -

      マッピング公式は、連結リストのノード参照(ポインタ)と同様の役割を果たします。配列内の任意のノードが与えられたとき、マッピング公式を使用してその左(右)の子ノードにアクセスできます。

      -

      7.3.2   任意の二分木の表現

      -

      完全二分木は特別なケースです。二分木の中間レベルには多くのNone値が存在することがよくあります。レベル順探索のシーケンスにはこれらのNone値が含まれないため、このシーケンスだけに依存してNone値の数と分布を推測することはできません。つまり、複数の二分木構造が同じレベル順探索シーケンスと一致する可能性があります

      -

      下図に示すように、完全でない二分木が与えられた場合、上記の配列表現方法は失敗します。

      -

      レベル順探索シーケンスが複数の二分木の可能性に対応

      -

      図 7-13   レベル順探索シーケンスが複数の二分木の可能性に対応

      +

      対応式は、連結リストにおけるノード参照(ポインタ)と同じ役割を果たします。与えられた配列内の任意のノードについて、この対応式を使えばその左(右)子ノードにアクセスできます。

      +

      7.3.2   任意の二分木を表現する

      +

      充足二分木は特殊なケースであり、一般の二分木では中間層に多数の None が存在することがよくあります。レベル順走査の列にはこれらの None が含まれないため、その列だけから None の数や分布位置を推定することはできません。つまり、このレベル順走査列に一致する二分木構造は複数存在し得ます

      +

      次の図のように、非充足二分木が与えられると、上記の配列表現はすでに成り立ちません。

      +

      レベル順走査列に対応する複数の二分木の可能性

      +

      図 7-13   レベル順走査列に対応する複数の二分木の可能性

      -

      この問題を解決するために、レベル順探索シーケンスですべてのNone値を明示的に書き出すことを検討できます。下図に示すように、この処理後、レベル順探索シーケンスは二分木を一意に表現できます。サンプルコードは以下の通りです:

      +

      この問題を解決するために、**レベル順走査列にすべての None を明示的に書き込む**ことを考えられます。次の図のように、このように処理すればレベル順走査列で二分木を一意に表現できます。コード例は以下のとおりです:

      # 二分木の配列表現
      -# Noneを使用して空のスロットを表現
      +# 空き位置を表すために None を使う
       tree = [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15]
       
      /* 二分木の配列表現 */
      -// 最大整数値INT_MAXを使用して空のスロットをマーク
      +// int の最大値 INT_MAX を使って空き位置を示す
       vector<int> tree = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15};
       
      /* 二分木の配列表現 */
      -// Integerラッパークラスを使用してnullで空のスロットをマーク
      +// int のラッパークラス Integer を使えば、null で空き位置を示せる
       Integer[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 };
       
      /* 二分木の配列表現 */
      -// nullable int (int?)を使用してnullで空のスロットをマーク
      +// nullable な int? 型を使えば、null で空き位置を示せる
       int?[] tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];
       
      /* 二分木の配列表現 */
      -// any型スライスを使用してnilで空のスロットをマーク
      +// any 型のスライスを使えば、nil で空き位置を示せる
       tree := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15}
       
      /* 二分木の配列表現 */
      -// optional Int (Int?)を使用してnilで空のスロットをマーク
      +// nullable な Int? 型を使えば、nil で空き位置を示せる
       let tree: [Int?] = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15]
       
      /* 二分木の配列表現 */
      -// nullを使用して空のスロットを表現
      +// null を使って空き位置を表す
       let tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];
       
      /* 二分木の配列表現 */
      -// nullを使用して空のスロットを表現
      +// null を使って空き位置を表す
       let tree: (number | null)[] = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];
       
      /* 二分木の配列表現 */
      -// nullable int (int?)を使用してnullで空のスロットをマーク
      +// nullable な int? 型を使えば、null で空き位置を示せる
       List<int?> tree = [1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15];
       
      /* 二分木の配列表現 */
      -// Noneを使用して空のスロットをマーク
      +// None を使って空き位置を示す
       let tree = [Some(1), Some(2), Some(3), Some(4), None, Some(6), Some(7), Some(8), Some(9), None, None, Some(12), None, None, Some(15)];
       
      /* 二分木の配列表現 */
      -// 最大int値を使用して空のスロットをマーク、したがってノード値はINT_MAXであってはならない
      +// int の最大値で空き位置を示すため、ノード値は INT_MAX であってはならない
       int tree[] = {1, 2, 3, 4, INT_MAX, 6, 7, 8, 9, INT_MAX, INT_MAX, 12, INT_MAX, INT_MAX, 15};
       
      /* 二分木の配列表現 */
      -// nullを使用して空のスロットを表現
      -val tree = mutableListOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 )
      +// null を使って空き位置を表す
      +val tree = arrayOf( 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 )
       
      -
      
      +
      ### 二分木の配列表現 ###
      +# nil を使って空き位置を表す
      +tree = [1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15]
       
      -

      任意の種類の二分木の配列表現

      -

      図 7-14   任意の種類の二分木の配列表現

      +

      任意の二分木の配列表現

      +

      図 7-14   任意の二分木の配列表現

      -

      注目すべきは、完備二分木は配列表現に非常に適している**ということです。完備二分木の定義を思い出すと、Noneは最下位レベルでのみ、かつ右側に向かって現れます。**つまり、すべてのNone値は確実にレベル順探索シーケンスの最後に現れます

      -

      これは、配列を使用して完備二分木を表現する際、すべてのNone値の格納を省略できることを意味し、非常に便利です。下図に例を示します。

      -

      完備二分木の配列表現

      -

      図 7-15   完備二分木の配列表現

      +

      補足すると、完全二分木は配列による表現に非常に適しています。完全二分木の定義を振り返ると、None は最下層の右側にしか現れないため、すべての None は必ずレベル順走査列の末尾に現れます

      +

      つまり、完全二分木を配列で表す場合は、すべての None の格納を省略できるため、非常に便利です。次の図に例を示します。

      +

      完全二分木の配列表現

      +

      図 7-15   完全二分木の配列表現

      -

      以下のコードは、配列表現に基づく二分木を実装し、次の操作を含みます:

      +

      以下のコードでは、配列ベースで表現した二分木を実装しており、次の操作を含みます。

        -
      • ノードが与えられたとき、その値、左(右)の子ノード、および親ノードを取得する。
      • -
      • 前順、中順、後順、およびレベル順探索シーケンスを取得する。
      • +
      • あるノードが与えられたとき、その値、左(右)子ノード、親ノードを取得する。
      • +
      • 前順走査、中順走査、後順走査、レベル順走査の列を取得する。
      array_binary_tree.py
      class ArrayBinaryTree:
      -    """配列ベースの二分木クラス"""
      +    """配列表現による二分木クラス"""
       
           def __init__(self, arr: list[int | None]):
               """コンストラクタ"""
               self._tree = list(arr)
       
           def size(self):
      -        """リストの容量"""
      +        """リスト容量"""
               return len(self._tree)
       
           def val(self, i: int) -> int | None:
      -        """インデックスiのノードの値を取得"""
      -        # インデックスが範囲外の場合、Noneを返し、空席を表す
      +        """インデックス i のノードの値を取得"""
      +        # インデックスが範囲外なら、空きを表す None を返す
               if i < 0 or i >= self.size():
                   return None
               return self._tree[i]
       
           def left(self, i: int) -> int | None:
      -        """インデックスiのノードの左の子のインデックスを取得"""
      +        """インデックス i のノードの左子ノードのインデックスを取得"""
               return 2 * i + 1
       
           def right(self, i: int) -> int | None:
      -        """インデックスiのノードの右の子のインデックスを取得"""
      +        """インデックス i のノードの右子ノードのインデックスを取得"""
               return 2 * i + 2
       
           def parent(self, i: int) -> int | None:
      -        """インデックスiのノードの親のインデックスを取得"""
      +        """インデックス i のノードの親ノードのインデックスを取得"""
               return (i - 1) // 2
       
           def level_order(self) -> list[int]:
               """レベル順走査"""
               self.res = []
      -        # 配列を走査
      +        # 配列を直接走査する
               for i in range(self.size()):
                   if self.val(i) is not None:
                       self.res.append(self.val(i))
               return self.res
       
           def dfs(self, i: int, order: str):
      -        """深さ優先走査"""
      +        """深さ優先探索"""
               if self.val(i) is None:
                   return
      -        # 前順走査
      +        # 先行順走査
               if order == "pre":
                   self.res.append(self.val(i))
               self.dfs(self.left(i), order)
      @@ -4545,7 +4547,7 @@
                   self.res.append(self.val(i))
       
           def pre_order(self) -> list[int]:
      -        """前順走査"""
      +        """先行順走査"""
               self.res = []
               self.dfs(0, order="pre")
               return self.res
      @@ -4564,7 +4566,7 @@
       
      -
      array_binary_tree.cpp
      /* 配列ベースの二分木クラス */
      +
      array_binary_tree.cpp
      /* 配列表現による二分木クラス */
       class ArrayBinaryTree {
         public:
           /* コンストラクタ */
      @@ -4572,30 +4574,30 @@
               tree = arr;
           }
       
      -    /* リストの容量 */
      +    /* リスト容量 */
           int size() {
               return tree.size();
           }
       
           /* インデックス i のノードの値を取得 */
           int val(int i) {
      -        // インデックスが範囲外の場合、INT_MAX を返す(null を表す)
      +        // インデックスが範囲外なら、空きを表す INT_MAX を返す
               if (i < 0 || i >= size())
                   return INT_MAX;
               return tree[i];
           }
       
      -    /* インデックス i のノードの左の子のインデックスを取得 */
      +    /* インデックス i のノードの左子ノードのインデックスを取得 */
           int left(int i) {
               return 2 * i + 1;
           }
       
      -    /* インデックス i のノードの右の子のインデックスを取得 */
      +    /* インデックス i のノードの右子ノードのインデックスを取得 */
           int right(int i) {
               return 2 * i + 2;
           }
       
      -    /* インデックス i のノードの親のインデックスを取得 */
      +    /* インデックス i のノードの親ノードのインデックスを取得 */
           int parent(int i) {
               return (i - 1) / 2;
           }
      @@ -4603,7 +4605,7 @@
           /* レベル順走査 */
           vector<int> levelOrder() {
               vector<int> res;
      -        // 配列を走査
      +        // 配列を直接走査する
               for (int i = 0; i < size(); i++) {
                   if (val(i) != INT_MAX)
                       res.push_back(val(i));
      @@ -4611,7 +4613,7 @@
               return res;
           }
       
      -    /* 前順走査 */
      +    /* 先行順走査 */
           vector<int> preOrder() {
               vector<int> res;
               dfs(0, "pre", res);
      @@ -4635,12 +4637,12 @@
         private:
           vector<int> tree;
       
      -    /* 深さ優先走査 */
      +    /* 深さ優先探索 */
           void dfs(int i, string order, vector<int> &res) {
      -        // 空の位置の場合、戻る
      +        // 空きスロットなら返す
               if (val(i) == INT_MAX)
                   return;
      -        // 前順走査
      +        // 先行順走査
               if (order == "pre")
                   res.push_back(val(i));
               dfs(left(i), order, res);
      @@ -4656,7 +4658,7 @@
       
      -
      array_binary_tree.java
      /* 配列ベースの二分木クラス */
      +
      array_binary_tree.java
      /* 配列表現による二分木クラス */
       class ArrayBinaryTree {
           private List<Integer> tree;
       
      @@ -4665,30 +4667,30 @@
               tree = new ArrayList<>(arr);
           }
       
      -    /* リストの容量 */
      +    /* リスト容量 */
           public int size() {
               return tree.size();
           }
       
           /* インデックス i のノードの値を取得 */
           public Integer val(int i) {
      -        // インデックスが範囲外の場合、null を返す(空の位置を表す)
      +        // インデックスが範囲外なら、空きを表す null を返す
               if (i < 0 || i >= size())
                   return null;
               return tree.get(i);
           }
       
      -    /* インデックス i のノードの左の子のインデックスを取得 */
      +    /* インデックス i のノードの左子ノードのインデックスを取得 */
           public Integer left(int i) {
               return 2 * i + 1;
           }
       
      -    /* インデックス i のノードの右の子のインデックスを取得 */
      +    /* インデックス i のノードの右子ノードのインデックスを取得 */
           public Integer right(int i) {
               return 2 * i + 2;
           }
       
      -    /* インデックス i のノードの親のインデックスを取得 */
      +    /* インデックス i のノードの親ノードのインデックスを取得 */
           public Integer parent(int i) {
               return (i - 1) / 2;
           }
      @@ -4696,7 +4698,7 @@
           /* レベル順走査 */
           public List<Integer> levelOrder() {
               List<Integer> res = new ArrayList<>();
      -        // 配列を走査
      +        // 配列を直接走査する
               for (int i = 0; i < size(); i++) {
                   if (val(i) != null)
                       res.add(val(i));
      @@ -4704,12 +4706,12 @@
               return res;
           }
       
      -    /* 深さ優先走査 */
      +    /* 深さ優先探索 */
           private void dfs(Integer i, String order, List<Integer> res) {
      -        // 空の位置の場合、戻る
      +        // 空きスロットなら返す
               if (val(i) == null)
                   return;
      -        // 前順走査
      +        // 先行順走査
               if ("pre".equals(order))
                   res.add(val(i));
               dfs(left(i), order, res);
      @@ -4722,7 +4724,7 @@
                   res.add(val(i));
           }
       
      -    /* 前順走査 */
      +    /* 先行順走査 */
           public List<Integer> preOrder() {
               List<Integer> res = new ArrayList<>();
               dfs(0, "pre", res);
      @@ -4746,59 +4748,916 @@
       
      -
      array_binary_tree.cs
      [class]{ArrayBinaryTree}-[func]{}
      +
      array_binary_tree.cs
      /* 配列表現による二分木クラス */
      +class ArrayBinaryTree(List<int?> arr) {
      +    List<int?> tree = new(arr);
      +
      +    /* リスト容量 */
      +    public int Size() {
      +        return tree.Count;
      +    }
      +
      +    /* インデックス i のノードの値を取得 */
      +    public int? Val(int i) {
      +        // インデックスが範囲外なら、空きを表す null を返す
      +        if (i < 0 || i >= Size())
      +            return null;
      +        return tree[i];
      +    }
      +
      +    /* インデックス i のノードの左子ノードのインデックスを取得 */
      +    public int Left(int i) {
      +        return 2 * i + 1;
      +    }
      +
      +    /* インデックス i のノードの右子ノードのインデックスを取得 */
      +    public int Right(int i) {
      +        return 2 * i + 2;
      +    }
      +
      +    /* インデックス i のノードの親ノードのインデックスを取得 */
      +    public int Parent(int i) {
      +        return (i - 1) / 2;
      +    }
      +
      +    /* レベル順走査 */
      +    public List<int> LevelOrder() {
      +        List<int> res = [];
      +        // 配列を直接走査する
      +        for (int i = 0; i < Size(); i++) {
      +            if (Val(i).HasValue)
      +                res.Add(Val(i)!.Value);
      +        }
      +        return res;
      +    }
      +
      +    /* 深さ優先探索 */
      +    void DFS(int i, string order, List<int> res) {
      +        // 空きスロットなら返す
      +        if (!Val(i).HasValue)
      +            return;
      +        // 先行順走査
      +        if (order == "pre")
      +            res.Add(Val(i)!.Value);
      +        DFS(Left(i), order, res);
      +        // 中順走査
      +        if (order == "in")
      +            res.Add(Val(i)!.Value);
      +        DFS(Right(i), order, res);
      +        // 後順走査
      +        if (order == "post")
      +            res.Add(Val(i)!.Value);
      +    }
      +
      +    /* 先行順走査 */
      +    public List<int> PreOrder() {
      +        List<int> res = [];
      +        DFS(0, "pre", res);
      +        return res;
      +    }
      +
      +    /* 中順走査 */
      +    public List<int> InOrder() {
      +        List<int> res = [];
      +        DFS(0, "in", res);
      +        return res;
      +    }
      +
      +    /* 後順走査 */
      +    public List<int> PostOrder() {
      +        List<int> res = [];
      +        DFS(0, "post", res);
      +        return res;
      +    }
      +}
       
      -
      array_binary_tree.go
      [class]{arrayBinaryTree}-[func]{}
      +
      array_binary_tree.go
      /* 配列表現による二分木クラス */
      +type arrayBinaryTree struct {
      +    tree []any
      +}
      +
      +/* コンストラクタ */
      +func newArrayBinaryTree(arr []any) *arrayBinaryTree {
      +    return &arrayBinaryTree{
      +        tree: arr,
      +    }
      +}
      +
      +/* リスト容量 */
      +func (abt *arrayBinaryTree) size() int {
      +    return len(abt.tree)
      +}
      +
      +/* インデックス i のノードの値を取得 */
      +func (abt *arrayBinaryTree) val(i int) any {
      +    // インデックスが範囲外なら、空きを表す null を返す
      +    if i < 0 || i >= abt.size() {
      +        return nil
      +    }
      +    return abt.tree[i]
      +}
      +
      +/* インデックス i のノードの左子ノードのインデックスを取得 */
      +func (abt *arrayBinaryTree) left(i int) int {
      +    return 2*i + 1
      +}
      +
      +/* インデックス i のノードの右子ノードのインデックスを取得 */
      +func (abt *arrayBinaryTree) right(i int) int {
      +    return 2*i + 2
      +}
      +
      +/* インデックス i のノードの親ノードのインデックスを取得 */
      +func (abt *arrayBinaryTree) parent(i int) int {
      +    return (i - 1) / 2
      +}
      +
      +/* レベル順走査 */
      +func (abt *arrayBinaryTree) levelOrder() []any {
      +    var res []any
      +    // 配列を直接走査する
      +    for i := 0; i < abt.size(); i++ {
      +        if abt.val(i) != nil {
      +            res = append(res, abt.val(i))
      +        }
      +    }
      +    return res
      +}
      +
      +/* 深さ優先探索 */
      +func (abt *arrayBinaryTree) dfs(i int, order string, res *[]any) {
      +    // 空きスロットなら返す
      +    if abt.val(i) == nil {
      +        return
      +    }
      +    // 先行順走査
      +    if order == "pre" {
      +        *res = append(*res, abt.val(i))
      +    }
      +    abt.dfs(abt.left(i), order, res)
      +    // 中順走査
      +    if order == "in" {
      +        *res = append(*res, abt.val(i))
      +    }
      +    abt.dfs(abt.right(i), order, res)
      +    // 後順走査
      +    if order == "post" {
      +        *res = append(*res, abt.val(i))
      +    }
      +}
      +
      +/* 先行順走査 */
      +func (abt *arrayBinaryTree) preOrder() []any {
      +    var res []any
      +    abt.dfs(0, "pre", &res)
      +    return res
      +}
      +
      +/* 中順走査 */
      +func (abt *arrayBinaryTree) inOrder() []any {
      +    var res []any
      +    abt.dfs(0, "in", &res)
      +    return res
      +}
      +
      +/* 後順走査 */
      +func (abt *arrayBinaryTree) postOrder() []any {
      +    var res []any
      +    abt.dfs(0, "post", &res)
      +    return res
      +}
       
      -
      array_binary_tree.swift
      [class]{ArrayBinaryTree}-[func]{}
      +
      array_binary_tree.swift
      /* 配列表現による二分木クラス */
      +class ArrayBinaryTree {
      +    private var tree: [Int?]
      +
      +    /* コンストラクタ */
      +    init(arr: [Int?]) {
      +        tree = arr
      +    }
      +
      +    /* リスト容量 */
      +    func size() -> Int {
      +        tree.count
      +    }
      +
      +    /* インデックス i のノードの値を取得 */
      +    func val(i: Int) -> Int? {
      +        // インデックスが範囲外なら、空きを表す null を返す
      +        if i < 0 || i >= size() {
      +            return nil
      +        }
      +        return tree[i]
      +    }
      +
      +    /* インデックス i のノードの左子ノードのインデックスを取得 */
      +    func left(i: Int) -> Int {
      +        2 * i + 1
      +    }
      +
      +    /* インデックス i のノードの右子ノードのインデックスを取得 */
      +    func right(i: Int) -> Int {
      +        2 * i + 2
      +    }
      +
      +    /* インデックス i のノードの親ノードのインデックスを取得 */
      +    func parent(i: Int) -> Int {
      +        (i - 1) / 2
      +    }
      +
      +    /* レベル順走査 */
      +    func levelOrder() -> [Int] {
      +        var res: [Int] = []
      +        // 配列を直接走査する
      +        for i in 0 ..< size() {
      +            if let val = val(i: i) {
      +                res.append(val)
      +            }
      +        }
      +        return res
      +    }
      +
      +    /* 深さ優先探索 */
      +    private func dfs(i: Int, order: String, res: inout [Int]) {
      +        // 空きスロットなら返す
      +        guard let val = val(i: i) else {
      +            return
      +        }
      +        // 先行順走査
      +        if order == "pre" {
      +            res.append(val)
      +        }
      +        dfs(i: left(i: i), order: order, res: &res)
      +        // 中順走査
      +        if order == "in" {
      +            res.append(val)
      +        }
      +        dfs(i: right(i: i), order: order, res: &res)
      +        // 後順走査
      +        if order == "post" {
      +            res.append(val)
      +        }
      +    }
      +
      +    /* 先行順走査 */
      +    func preOrder() -> [Int] {
      +        var res: [Int] = []
      +        dfs(i: 0, order: "pre", res: &res)
      +        return res
      +    }
      +
      +    /* 中順走査 */
      +    func inOrder() -> [Int] {
      +        var res: [Int] = []
      +        dfs(i: 0, order: "in", res: &res)
      +        return res
      +    }
      +
      +    /* 後順走査 */
      +    func postOrder() -> [Int] {
      +        var res: [Int] = []
      +        dfs(i: 0, order: "post", res: &res)
      +        return res
      +    }
      +}
       
      -
      array_binary_tree.js
      [class]{ArrayBinaryTree}-[func]{}
      +
      array_binary_tree.js
      /* 配列表現による二分木クラス */
      +class ArrayBinaryTree {
      +    #tree;
      +
      +    /* コンストラクタ */
      +    constructor(arr) {
      +        this.#tree = arr;
      +    }
      +
      +    /* リスト容量 */
      +    size() {
      +        return this.#tree.length;
      +    }
      +
      +    /* インデックス i のノードの値を取得 */
      +    val(i) {
      +        // インデックスが範囲外なら、空きを表す null を返す
      +        if (i < 0 || i >= this.size()) return null;
      +        return this.#tree[i];
      +    }
      +
      +    /* インデックス i のノードの左子ノードのインデックスを取得 */
      +    left(i) {
      +        return 2 * i + 1;
      +    }
      +
      +    /* インデックス i のノードの右子ノードのインデックスを取得 */
      +    right(i) {
      +        return 2 * i + 2;
      +    }
      +
      +    /* インデックス i のノードの親ノードのインデックスを取得 */
      +    parent(i) {
      +        return Math.floor((i - 1) / 2); // 切り捨て除算
      +    }
      +
      +    /* レベル順走査 */
      +    levelOrder() {
      +        let res = [];
      +        // 配列を直接走査する
      +        for (let i = 0; i < this.size(); i++) {
      +            if (this.val(i) !== null) res.push(this.val(i));
      +        }
      +        return res;
      +    }
      +
      +    /* 深さ優先探索 */
      +    #dfs(i, order, res) {
      +        // 空きスロットなら返す
      +        if (this.val(i) === null) return;
      +        // 先行順走査
      +        if (order === 'pre') res.push(this.val(i));
      +        this.#dfs(this.left(i), order, res);
      +        // 中順走査
      +        if (order === 'in') res.push(this.val(i));
      +        this.#dfs(this.right(i), order, res);
      +        // 後順走査
      +        if (order === 'post') res.push(this.val(i));
      +    }
      +
      +    /* 先行順走査 */
      +    preOrder() {
      +        const res = [];
      +        this.#dfs(0, 'pre', res);
      +        return res;
      +    }
      +
      +    /* 中順走査 */
      +    inOrder() {
      +        const res = [];
      +        this.#dfs(0, 'in', res);
      +        return res;
      +    }
      +
      +    /* 後順走査 */
      +    postOrder() {
      +        const res = [];
      +        this.#dfs(0, 'post', res);
      +        return res;
      +    }
      +}
       
      -
      array_binary_tree.ts
      [class]{ArrayBinaryTree}-[func]{}
      +
      array_binary_tree.ts
      /* 配列表現による二分木クラス */
      +class ArrayBinaryTree {
      +    #tree: (number | null)[];
      +
      +    /* コンストラクタ */
      +    constructor(arr: (number | null)[]) {
      +        this.#tree = arr;
      +    }
      +
      +    /* リスト容量 */
      +    size(): number {
      +        return this.#tree.length;
      +    }
      +
      +    /* インデックス i のノードの値を取得 */
      +    val(i: number): number | null {
      +        // インデックスが範囲外なら、空きを表す null を返す
      +        if (i < 0 || i >= this.size()) return null;
      +        return this.#tree[i];
      +    }
      +
      +    /* インデックス i のノードの左子ノードのインデックスを取得 */
      +    left(i: number): number {
      +        return 2 * i + 1;
      +    }
      +
      +    /* インデックス i のノードの右子ノードのインデックスを取得 */
      +    right(i: number): number {
      +        return 2 * i + 2;
      +    }
      +
      +    /* インデックス i のノードの親ノードのインデックスを取得 */
      +    parent(i: number): number {
      +        return Math.floor((i - 1) / 2); // 切り捨て除算
      +    }
      +
      +    /* レベル順走査 */
      +    levelOrder(): number[] {
      +        let res = [];
      +        // 配列を直接走査する
      +        for (let i = 0; i < this.size(); i++) {
      +            if (this.val(i) !== null) res.push(this.val(i));
      +        }
      +        return res;
      +    }
      +
      +    /* 深さ優先探索 */
      +    #dfs(i: number, order: Order, res: (number | null)[]): void {
      +        // 空きスロットなら返す
      +        if (this.val(i) === null) return;
      +        // 先行順走査
      +        if (order === 'pre') res.push(this.val(i));
      +        this.#dfs(this.left(i), order, res);
      +        // 中順走査
      +        if (order === 'in') res.push(this.val(i));
      +        this.#dfs(this.right(i), order, res);
      +        // 後順走査
      +        if (order === 'post') res.push(this.val(i));
      +    }
      +
      +    /* 先行順走査 */
      +    preOrder(): (number | null)[] {
      +        const res = [];
      +        this.#dfs(0, 'pre', res);
      +        return res;
      +    }
      +
      +    /* 中順走査 */
      +    inOrder(): (number | null)[] {
      +        const res = [];
      +        this.#dfs(0, 'in', res);
      +        return res;
      +    }
      +
      +    /* 後順走査 */
      +    postOrder(): (number | null)[] {
      +        const res = [];
      +        this.#dfs(0, 'post', res);
      +        return res;
      +    }
      +}
       
      -
      array_binary_tree.dart
      [class]{ArrayBinaryTree}-[func]{}
      +
      array_binary_tree.dart
      /* 配列表現による二分木クラス */
      +class ArrayBinaryTree {
      +  late List<int?> _tree;
      +
      +  /* コンストラクタ */
      +  ArrayBinaryTree(this._tree);
      +
      +  /* リスト容量 */
      +  int size() {
      +    return _tree.length;
      +  }
      +
      +  /* インデックス i のノードの値を取得 */
      +  int? val(int i) {
      +    // インデックスが範囲外なら、空きを表す null を返す
      +    if (i < 0 || i >= size()) {
      +      return null;
      +    }
      +    return _tree[i];
      +  }
      +
      +  /* インデックス i のノードの左子ノードのインデックスを取得 */
      +  int? left(int i) {
      +    return 2 * i + 1;
      +  }
      +
      +  /* インデックス i のノードの右子ノードのインデックスを取得 */
      +  int? right(int i) {
      +    return 2 * i + 2;
      +  }
      +
      +  /* インデックス i のノードの親ノードのインデックスを取得 */
      +  int? parent(int i) {
      +    return (i - 1) ~/ 2;
      +  }
      +
      +  /* レベル順走査 */
      +  List<int> levelOrder() {
      +    List<int> res = [];
      +    for (int i = 0; i < size(); i++) {
      +      if (val(i) != null) {
      +        res.add(val(i)!);
      +      }
      +    }
      +    return res;
      +  }
      +
      +  /* 深さ優先探索 */
      +  void dfs(int i, String order, List<int?> res) {
      +    // 空きスロットなら返す
      +    if (val(i) == null) {
      +      return;
      +    }
      +    // 先行順走査
      +    if (order == 'pre') {
      +      res.add(val(i));
      +    }
      +    dfs(left(i)!, order, res);
      +    // 中順走査
      +    if (order == 'in') {
      +      res.add(val(i));
      +    }
      +    dfs(right(i)!, order, res);
      +    // 後順走査
      +    if (order == 'post') {
      +      res.add(val(i));
      +    }
      +  }
      +
      +  /* 先行順走査 */
      +  List<int?> preOrder() {
      +    List<int?> res = [];
      +    dfs(0, 'pre', res);
      +    return res;
      +  }
      +
      +  /* 中順走査 */
      +  List<int?> inOrder() {
      +    List<int?> res = [];
      +    dfs(0, 'in', res);
      +    return res;
      +  }
      +
      +  /* 後順走査 */
      +  List<int?> postOrder() {
      +    List<int?> res = [];
      +    dfs(0, 'post', res);
      +    return res;
      +  }
      +}
       
      -
      array_binary_tree.rs
      [class]{ArrayBinaryTree}-[func]{}
      +
      array_binary_tree.rs
      /* 配列表現による二分木クラス */
      +struct ArrayBinaryTree {
      +    tree: Vec<Option<i32>>,
      +}
      +
      +impl ArrayBinaryTree {
      +    /* コンストラクタ */
      +    fn new(arr: Vec<Option<i32>>) -> Self {
      +        Self { tree: arr }
      +    }
      +
      +    /* リスト容量 */
      +    fn size(&self) -> i32 {
      +        self.tree.len() as i32
      +    }
      +
      +    /* インデックス i のノードの値を取得 */
      +    fn val(&self, i: i32) -> Option<i32> {
      +        // インデックスが範囲外なら、空きを表す None を返す
      +        if i < 0 || i >= self.size() {
      +            None
      +        } else {
      +            self.tree[i as usize]
      +        }
      +    }
      +
      +    /* インデックス i のノードの左子ノードのインデックスを取得 */
      +    fn left(&self, i: i32) -> i32 {
      +        2 * i + 1
      +    }
      +
      +    /* インデックス i のノードの右子ノードのインデックスを取得 */
      +    fn right(&self, i: i32) -> i32 {
      +        2 * i + 2
      +    }
      +
      +    /* インデックス i のノードの親ノードのインデックスを取得 */
      +    fn parent(&self, i: i32) -> i32 {
      +        (i - 1) / 2
      +    }
      +
      +    /* レベル順走査 */
      +    fn level_order(&self) -> Vec<i32> {
      +        self.tree.iter().filter_map(|&x| x).collect()
      +    }
      +
      +    /* 深さ優先探索 */
      +    fn dfs(&self, i: i32, order: &'static str, res: &mut Vec<i32>) {
      +        if self.val(i).is_none() {
      +            return;
      +        }
      +        let val = self.val(i).unwrap();
      +        // 先行順走査
      +        if order == "pre" {
      +            res.push(val);
      +        }
      +        self.dfs(self.left(i), order, res);
      +        // 中順走査
      +        if order == "in" {
      +            res.push(val);
      +        }
      +        self.dfs(self.right(i), order, res);
      +        // 後順走査
      +        if order == "post" {
      +            res.push(val);
      +        }
      +    }
      +
      +    /* 先行順走査 */
      +    fn pre_order(&self) -> Vec<i32> {
      +        let mut res = vec![];
      +        self.dfs(0, "pre", &mut res);
      +        res
      +    }
      +
      +    /* 中順走査 */
      +    fn in_order(&self) -> Vec<i32> {
      +        let mut res = vec![];
      +        self.dfs(0, "in", &mut res);
      +        res
      +    }
      +
      +    /* 後順走査 */
      +    fn post_order(&self) -> Vec<i32> {
      +        let mut res = vec![];
      +        self.dfs(0, "post", &mut res);
      +        res
      +    }
      +}
       
      -
      array_binary_tree.c
      [class]{ArrayBinaryTree}-[func]{}
      +
      array_binary_tree.c
      /* 配列表現による二分木の構造体 */
      +typedef struct {
      +    int *tree;
      +    int size;
      +} ArrayBinaryTree;
      +
      +/* コンストラクタ */
      +ArrayBinaryTree *newArrayBinaryTree(int *arr, int arrSize) {
      +    ArrayBinaryTree *abt = (ArrayBinaryTree *)malloc(sizeof(ArrayBinaryTree));
      +    abt->tree = malloc(sizeof(int) * arrSize);
      +    memcpy(abt->tree, arr, sizeof(int) * arrSize);
      +    abt->size = arrSize;
      +    return abt;
      +}
      +
      +/* デストラクタ */
      +void delArrayBinaryTree(ArrayBinaryTree *abt) {
      +    free(abt->tree);
      +    free(abt);
      +}
      +
      +/* リスト容量 */
      +int size(ArrayBinaryTree *abt) {
      +    return abt->size;
      +}
      +
      +/* インデックス i のノードの値を取得 */
      +int val(ArrayBinaryTree *abt, int i) {
      +    // インデックスが範囲外なら、空きを表す INT_MAX を返す
      +    if (i < 0 || i >= size(abt))
      +        return INT_MAX;
      +    return abt->tree[i];
      +}
      +
      +/* レベル順走査 */
      +int *levelOrder(ArrayBinaryTree *abt, int *returnSize) {
      +    int *res = (int *)malloc(sizeof(int) * size(abt));
      +    int index = 0;
      +    // 配列を直接走査する
      +    for (int i = 0; i < size(abt); i++) {
      +        if (val(abt, i) != INT_MAX)
      +            res[index++] = val(abt, i);
      +    }
      +    *returnSize = index;
      +    return res;
      +}
      +
      +/* 深さ優先探索 */
      +void dfs(ArrayBinaryTree *abt, int i, char *order, int *res, int *index) {
      +    // 空きスロットなら返す
      +    if (val(abt, i) == INT_MAX)
      +        return;
      +    // 先行順走査
      +    if (strcmp(order, "pre") == 0)
      +        res[(*index)++] = val(abt, i);
      +    dfs(abt, left(i), order, res, index);
      +    // 中順走査
      +    if (strcmp(order, "in") == 0)
      +        res[(*index)++] = val(abt, i);
      +    dfs(abt, right(i), order, res, index);
      +    // 後順走査
      +    if (strcmp(order, "post") == 0)
      +        res[(*index)++] = val(abt, i);
      +}
      +
      +/* 先行順走査 */
      +int *preOrder(ArrayBinaryTree *abt, int *returnSize) {
      +    int *res = (int *)malloc(sizeof(int) * size(abt));
      +    int index = 0;
      +    dfs(abt, 0, "pre", res, &index);
      +    *returnSize = index;
      +    return res;
      +}
      +
      +/* 中順走査 */
      +int *inOrder(ArrayBinaryTree *abt, int *returnSize) {
      +    int *res = (int *)malloc(sizeof(int) * size(abt));
      +    int index = 0;
      +    dfs(abt, 0, "in", res, &index);
      +    *returnSize = index;
      +    return res;
      +}
      +
      +/* 後順走査 */
      +int *postOrder(ArrayBinaryTree *abt, int *returnSize) {
      +    int *res = (int *)malloc(sizeof(int) * size(abt));
      +    int index = 0;
      +    dfs(abt, 0, "post", res, &index);
      +    *returnSize = index;
      +    return res;
      +}
       
      -
      array_binary_tree.kt
      [class]{ArrayBinaryTree}-[func]{}
      +
      array_binary_tree.kt
      /* 配列表現による二分木クラス */
      +class ArrayBinaryTree(val tree: MutableList<Int?>) {
      +    /* リスト容量 */
      +    fun size(): Int {
      +        return tree.size
      +    }
      +
      +    /* インデックス i のノードの値を取得 */
      +    fun _val(i: Int): Int? {
      +        // インデックスが範囲外なら、空きを表す null を返す
      +        if (i < 0 || i >= size()) return null
      +        return tree[i]
      +    }
      +
      +    /* インデックス i のノードの左子ノードのインデックスを取得 */
      +    fun left(i: Int): Int {
      +        return 2 * i + 1
      +    }
      +
      +    /* インデックス i のノードの右子ノードのインデックスを取得 */
      +    fun right(i: Int): Int {
      +        return 2 * i + 2
      +    }
      +
      +    /* インデックス i のノードの親ノードのインデックスを取得 */
      +    fun parent(i: Int): Int {
      +        return (i - 1) / 2
      +    }
      +
      +    /* レベル順走査 */
      +    fun levelOrder(): MutableList<Int?> {
      +        val res = mutableListOf<Int?>()
      +        // 配列を直接走査する
      +        for (i in 0..<size()) {
      +            if (_val(i) != null)
      +                res.add(_val(i))
      +        }
      +        return res
      +    }
      +
      +    /* 深さ優先探索 */
      +    fun dfs(i: Int, order: String, res: MutableList<Int?>) {
      +        // 空きスロットなら返す
      +        if (_val(i) == null)
      +            return
      +        // 先行順走査
      +        if ("pre" == order)
      +            res.add(_val(i))
      +        dfs(left(i), order, res)
      +        // 中順走査
      +        if ("in" == order)
      +            res.add(_val(i))
      +        dfs(right(i), order, res)
      +        // 後順走査
      +        if ("post" == order)
      +            res.add(_val(i))
      +    }
      +
      +    /* 先行順走査 */
      +    fun preOrder(): MutableList<Int?> {
      +        val res = mutableListOf<Int?>()
      +        dfs(0, "pre", res)
      +        return res
      +    }
      +
      +    /* 中順走査 */
      +    fun inOrder(): MutableList<Int?> {
      +        val res = mutableListOf<Int?>()
      +        dfs(0, "in", res)
      +        return res
      +    }
      +
      +    /* 後順走査 */
      +    fun postOrder(): MutableList<Int?> {
      +        val res = mutableListOf<Int?>()
      +        dfs(0, "post", res)
      +        return res
      +    }
      +}
       
      -
      array_binary_tree.rb
      [class]{ArrayBinaryTree}-[func]{}
      +
      array_binary_tree.rb
      ### 配列表現の二分木クラス ###
      +class ArrayBinaryTree
      +  ### コンストラクタ ###
      +  def initialize(arr)
      +    @tree = arr.to_a
      +  end
      +
      +  ### リスト容量 ###
      +  def size
      +    @tree.length
      +  end
      +
      +  ### インデックス i のノードの値を取得 ###
      +  def val(i)
      +    # インデックスが範囲外なら `nil` を返し、空きスロットを表す
      +    return if i < 0 || i >= size
      +
      +    @tree[i]
      +  end
      +
      +  ### インデックス i のノードの左子ノードのインデックスを取得 ###
      +  def left(i)
      +    2 * i + 1
      +  end
      +
      +  ### インデックス i のノードの右子ノードのインデックスを取得 ###
      +  def right(i)
      +    2 * i + 2
      +  end
      +
      +  ### インデックス i のノードの親ノードのインデックスを取得 ###
      +  def parent(i)
      +    (i - 1) / 2
      +  end
      +
      +  ### レベル順走査 ###
      +  def level_order
      +    @res = []
      +
      +    # 配列を直接走査する
      +    for i in 0...size
      +      @res << val(i) unless val(i).nil?
      +    end
      +
      +    @res
      +  end
      +
      +  ### 深さ優先探索 ###
      +  def dfs(i, order)
      +    return if val(i).nil?
      +    # 先行順走査
      +    @res << val(i) if order == :pre
      +    dfs(left(i), order)
      +    # 中順走査
      +    @res << val(i) if order == :in
      +    dfs(right(i), order)
      +    # 後順走査
      +    @res << val(i) if order == :post
      +  end
      +
      +  ### 前順走査 ###
      +  def pre_order
      +    @res = []
      +    dfs(0, :pre)
      +    @res
      +  end
      +
      +  ### 中順走査 ###
      +  def in_order
      +    @res = []
      +    dfs(0, :in)
      +    @res
      +  end
      +
      +  ### 後順走査 ###
      +  def post_order
      +    @res = []
      +    dfs(0, :post)
      +    @res
      +  end
      +end
       
      -

      7.3.3   利点と制限

      -

      二分木の配列表現には以下の利点があります:

      +
      +コードの可視化 +

      +

      +
      +

      7.3.3   利点と制約

      +

      二分木の配列表現には主に次の利点があります。

        -
      • 配列は連続したメモリ空間に格納されるため、キャッシュフレンドリーで、より高速なアクセスと探索が可能です。
      • -
      • ポインタを格納する必要がないため、スペースを節約できます。
      • -
      • ノードへのランダムアクセスが可能です。
      • +
      • 配列は連続したメモリ空間に格納されるため、キャッシュ効率が高く、アクセスと走査が速い。
      • +
      • ポインタを格納する必要がなく、比較的省スペースである。
      • +
      • ノードへのランダムアクセスが可能である。
      -

      しかし、配列表現にはいくつかの制限もあります:

      +

      ただし、配列表現にはいくつかの制約もあります。

        -
      • 配列格納には連続したメモリ空間が必要なため、大量のデータを持つ木の格納には適していません。
      • -
      • ノードの追加や削除には配列の挿入や削除操作が必要で、効率が低くなります。
      • -
      • 二分木に多くのNone値がある場合、配列に含まれるノードデータの割合が低くなり、空間利用率が低下します。
      • +
      • 配列による格納には連続したメモリ空間が必要なため、データ量が大きすぎる木の格納には向かない。
      • +
      • ノードの追加と削除は配列の挿入・削除操作で実現する必要があり、効率は低い。
      • +
      • 二分木に大量の None が存在すると、配列に占める実ノードデータの比率が低くなり、空間利用率も低下する。
      diff --git a/ja/chapter_tree/avl_tree/index.html b/ja/chapter_tree/avl_tree/index.html index ad689e7d3..02d375913 100644 --- a/ja/chapter_tree/avl_tree/index.html +++ b/ja/chapter_tree/avl_tree/index.html @@ -6,7 +6,7 @@ - + @@ -39,7 +39,7 @@ - 7.5 AVL木 * - Hello アルゴリズム + 7.5 AVL 木 * - Hello アルゴリズム @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
      @@ -152,7 +152,7 @@
      - 7.5   AVL木 * + 7.5   AVL 木 *
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1591,7 +1591,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1613,7 +1613,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1635,7 +1635,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1953,7 +1953,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -1971,7 +1971,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2001,12 +2001,12 @@ - 7.5.1   AVL木の一般的な用語 + 7.5.1   AVL 木の基本用語 -
      -
      avl_tree.cs
      [class]{AVLTree}-[func]{Height}
      -
      -[class]{AVLTree}-[func]{UpdateHeight}
      +
      avl_tree.cs
      /* ノードの高さを取得 */
      +int Height(TreeNode? node) {
      +    // 空ノードの高さは -1、葉ノードの高さは 0
      +    return node == null ? -1 : node.height;
      +}
      +
      +/* ノードの高さを更新する */
      +void UpdateHeight(TreeNode node) {
      +    // ノードの高さは最も高い部分木の高さ + 1 に等しい
      +    node.height = Math.Max(Height(node.left), Height(node.right)) + 1;
      +}
       
      -
      avl_tree.go
      [class]{aVLTree}-[func]{height}
      -
      -[class]{aVLTree}-[func]{updateHeight}
      +
      avl_tree.go
      /* ノードの高さを取得 */
      +func (t *aVLTree) height(node *TreeNode) int {
      +    // 空ノードの高さは -1、葉ノードの高さは 0
      +    if node != nil {
      +        return node.Height
      +    }
      +    return -1
      +}
      +
      +/* ノードの高さを更新する */
      +func (t *aVLTree) updateHeight(node *TreeNode) {
      +    lh := t.height(node.Left)
      +    rh := t.height(node.Right)
      +    // ノードの高さは最も高い部分木の高さ + 1 に等しい
      +    if lh > rh {
      +        node.Height = lh + 1
      +    } else {
      +        node.Height = rh + 1
      +    }
      +}
       
      -
      avl_tree.swift
      [class]{AVLTree}-[func]{height}
      -
      -[class]{AVLTree}-[func]{updateHeight}
      +
      avl_tree.swift
      /* ノードの高さを取得 */
      +func height(node: TreeNode?) -> Int {
      +    // 空ノードの高さは -1、葉ノードの高さは 0
      +    node?.height ?? -1
      +}
      +
      +/* ノードの高さを更新する */
      +func updateHeight(node: TreeNode?) {
      +    // ノードの高さは最も高い部分木の高さ + 1 に等しい
      +    node?.height = max(height(node: node?.left), height(node: node?.right)) + 1
      +}
       
      -
      avl_tree.js
      [class]{AVLTree}-[func]{height}
      -
      -[class]{AVLTree}-[func]{updateHeight}
      +
      avl_tree.js
      /* ノードの高さを取得 */
      +height(node) {
      +    // 空ノードの高さは -1、葉ノードの高さは 0
      +    return node === null ? -1 : node.height;
      +}
      +
      +/* ノードの高さを更新する */
      +#updateHeight(node) {
      +    // ノードの高さは最も高い部分木の高さ + 1 に等しい
      +    node.height =
      +        Math.max(this.height(node.left), this.height(node.right)) + 1;
      +}
       
      -
      avl_tree.ts
      [class]{AVLTree}-[func]{height}
      -
      -[class]{AVLTree}-[func]{updateHeight}
      +
      avl_tree.ts
      /* ノードの高さを取得 */
      +height(node: TreeNode): number {
      +    // 空ノードの高さは -1、葉ノードの高さは 0
      +    return node === null ? -1 : node.height;
      +}
      +
      +/* ノードの高さを更新する */
      +updateHeight(node: TreeNode): void {
      +    // ノードの高さは最も高い部分木の高さ + 1 に等しい
      +    node.height =
      +        Math.max(this.height(node.left), this.height(node.right)) + 1;
      +}
       
      -
      avl_tree.dart
      [class]{AVLTree}-[func]{height}
      -
      -[class]{AVLTree}-[func]{updateHeight}
      +
      avl_tree.dart
      /* ノードの高さを取得 */
      +int height(TreeNode? node) {
      +  // 空ノードの高さは -1、葉ノードの高さは 0
      +  return node == null ? -1 : node.height;
      +}
      +
      +/* ノードの高さを更新する */
      +void updateHeight(TreeNode? node) {
      +  // ノードの高さは最も高い部分木の高さ + 1 に等しい
      +  node!.height = max(height(node.left), height(node.right)) + 1;
      +}
       
      -
      avl_tree.rs
      [class]{AVLTree}-[func]{height}
      -
      -[class]{AVLTree}-[func]{update_height}
      +
      avl_tree.rs
      /* ノードの高さを取得 */
      +fn height(node: OptionTreeNodeRc) -> i32 {
      +    // 空ノードの高さは -1、葉ノードの高さは 0
      +    match node {
      +        Some(node) => node.borrow().height,
      +        None => -1,
      +    }
      +}
      +
      +/* ノードの高さを更新する */
      +fn update_height(node: OptionTreeNodeRc) {
      +    if let Some(node) = node {
      +        let left = node.borrow().left.clone();
      +        let right = node.borrow().right.clone();
      +        // ノードの高さは最も高い部分木の高さ + 1 に等しい
      +        node.borrow_mut().height = std::cmp::max(Self::height(left), Self::height(right)) + 1;
      +    }
      +}
       
      -
      avl_tree.c
      [class]{}-[func]{height}
      -
      -[class]{}-[func]{updateHeight}
      +
      avl_tree.c
      /* ノードの高さを取得 */
      +int height(TreeNode *node) {
      +    // 空ノードの高さは -1、葉ノードの高さは 0
      +    if (node != NULL) {
      +        return node->height;
      +    }
      +    return -1;
      +}
      +
      +/* ノードの高さを更新する */
      +void updateHeight(TreeNode *node) {
      +    int lh = height(node->left);
      +    int rh = height(node->right);
      +    // ノードの高さは最も高い部分木の高さ + 1 に等しい
      +    if (lh > rh) {
      +        node->height = lh + 1;
      +    } else {
      +        node->height = rh + 1;
      +    }
      +}
       
      -
      avl_tree.kt
      [class]{AVLTree}-[func]{height}
      -
      -[class]{AVLTree}-[func]{updateHeight}
      +
      avl_tree.kt
      /* ノードの高さを取得 */
      +fun height(node: TreeNode?): Int {
      +    // 空ノードの高さは -1、葉ノードの高さは 0
      +    return node?.height ?: -1
      +}
      +
      +/* ノードの高さを更新する */
      +fun updateHeight(node: TreeNode?) {
      +    // ノードの高さは最も高い部分木の高さ + 1 に等しい
      +    node?.height = max(height(node?.left), height(node?.right)) + 1
      +}
       
      -
      avl_tree.rb
      [class]{AVLTree}-[func]{height}
      -
      -[class]{AVLTree}-[func]{update_height}
      +
      avl_tree.rb
      ### ノードの高さを取得 ###
      +def height(node)
      +  # 空ノードの高さは -1、葉ノードの高さは 0
      +  return node.height unless node.nil?
      +
      +  -1
      +end
      +
      +### ノードの高さを更新 ###
      +def update_height(node)
      +  # ノードの高さは最も高い部分木の高さ + 1 に等しい
      +  node.height = [height(node.left), height(node.right)].max + 1
      +end
       
      -

      2.   ノードの平衡因子

      -

      ノードの平衡因子は、そのノードの左部分木の高さから右部分木の高さを引いた値として定義され、nullノードの平衡因子は\(0\)として定義されます。後で使いやすくするため、ノードの平衡因子を取得する機能も関数にカプセル化します:

      +

      2.   ノードの平衡係数

      +

      ノードの平衡係数(balance factor)は、左部分木の高さから右部分木の高さを引いた値と定義し、空ノードの平衡係数は \(0\) とします。同様に、ノードの平衡係数を取得する機能も関数にカプセル化して、後続で使いやすくします:

      avl_tree.py
      def balance_factor(self, node: TreeNode | None) -> int:
      -    """バランス因子を取得"""
      -    # 空ノードのバランス因子は0
      +    """平衡係数を取得"""
      +    # 空ノードの平衡係数は 0
           if node is None:
               return 0
      -    # ノードのバランス因子 = 左部分木の高さ - 右部分木の高さ
      +    # ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ
           return self.height(node.left) - self.height(node.right)
       
      -
      avl_tree.cpp
      /* 平衡因子を取得 */
      +
      avl_tree.cpp
      /* 平衡係数を取得 */
       int balanceFactor(TreeNode *node) {
      -    // 空ノードの平衡因子は0
      +    // 空ノードの平衡係数は 0
           if (node == nullptr)
               return 0;
      -    // ノードの平衡因子 = 左部分木の高さ - 右部分木の高さ
      +    // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ
           return height(node->left) - height(node->right);
       }
       
      -
      avl_tree.java
      /* 平衡因子を取得 */
      +
      avl_tree.java
      /* 平衡係数を取得 */
       int balanceFactor(TreeNode node) {
      -    // 空ノードの平衡因子は 0
      +    // 空ノードの平衡係数は 0
           if (node == null)
               return 0;
      -    // ノードの平衡因子 = 左部分木の高さ - 右部分木の高さ
      +    // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ
           return height(node.left) - height(node.right);
       }
       
      -
      avl_tree.cs
      [class]{AVLTree}-[func]{BalanceFactor}
      +
      avl_tree.cs
      /* 平衡係数を取得 */
      +int BalanceFactor(TreeNode? node) {
      +    // 空ノードの平衡係数は 0
      +    if (node == null) return 0;
      +    // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ
      +    return Height(node.left) - Height(node.right);
      +}
       
      -
      avl_tree.go
      [class]{aVLTree}-[func]{balanceFactor}
      +
      avl_tree.go
      /* 平衡係数を取得 */
      +func (t *aVLTree) balanceFactor(node *TreeNode) int {
      +    // 空ノードの平衡係数は 0
      +    if node == nil {
      +        return 0
      +    }
      +    // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ
      +    return t.height(node.Left) - t.height(node.Right)
      +}
       
      -
      avl_tree.swift
      [class]{AVLTree}-[func]{balanceFactor}
      +
      avl_tree.swift
      /* 平衡係数を取得 */
      +func balanceFactor(node: TreeNode?) -> Int {
      +    // 空ノードの平衡係数は 0
      +    guard let node = node else { return 0 }
      +    // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ
      +    return height(node: node.left) - height(node: node.right)
      +}
       
      -
      avl_tree.js
      [class]{AVLTree}-[func]{balanceFactor}
      +
      avl_tree.js
      /* 平衡係数を取得 */
      +balanceFactor(node) {
      +    // 空ノードの平衡係数は 0
      +    if (node === null) return 0;
      +    // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ
      +    return this.height(node.left) - this.height(node.right);
      +}
       
      -
      avl_tree.ts
      [class]{AVLTree}-[func]{balanceFactor}
      +
      avl_tree.ts
      /* 平衡係数を取得 */
      +balanceFactor(node: TreeNode): number {
      +    // 空ノードの平衡係数は 0
      +    if (node === null) return 0;
      +    // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ
      +    return this.height(node.left) - this.height(node.right);
      +}
       
      -
      avl_tree.dart
      [class]{AVLTree}-[func]{balanceFactor}
      +
      avl_tree.dart
      /* 平衡係数を取得 */
      +int balanceFactor(TreeNode? node) {
      +  // 空ノードの平衡係数は 0
      +  if (node == null) return 0;
      +  // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ
      +  return height(node.left) - height(node.right);
      +}
       
      -
      avl_tree.rs
      [class]{AVLTree}-[func]{balance_factor}
      +
      avl_tree.rs
      /* 平衡係数を取得 */
      +fn balance_factor(node: OptionTreeNodeRc) -> i32 {
      +    match node {
      +        // 空ノードの平衡係数は 0
      +        None => 0,
      +        // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ
      +        Some(node) => {
      +            Self::height(node.borrow().left.clone()) - Self::height(node.borrow().right.clone())
      +        }
      +    }
      +}
       
      -
      avl_tree.c
      [class]{}-[func]{balanceFactor}
      +
      avl_tree.c
      /* 平衡係数を取得 */
      +int balanceFactor(TreeNode *node) {
      +    // 空ノードの平衡係数は 0
      +    if (node == NULL) {
      +        return 0;
      +    }
      +    // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ
      +    return height(node->left) - height(node->right);
      +}
       
      -
      avl_tree.kt
      [class]{AVLTree}-[func]{balanceFactor}
      +
      avl_tree.kt
      /* 平衡係数を取得 */
      +fun balanceFactor(node: TreeNode?): Int {
      +    // 空ノードの平衡係数は 0
      +    if (node == null) return 0
      +    // ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ
      +    return height(node.left) - height(node.right)
      +}
       
      -
      avl_tree.rb
      [class]{AVLTree}-[func]{balance_factor}
      +
      avl_tree.rb
      ### 平衡係数を取得 ###
      +def balance_factor(node)
      +  # 空ノードの平衡係数は 0
      +  return 0 if node.nil?
      +
      +  # ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ
      +  height(node.left) - height(node.right)
      +end
       

      Tip

      -

      平衡因子を\(f\)とすると、AVL木の任意のノードの平衡因子は\(-1 \le f \le 1\)を満たします。

      +

      平衡係数を \(f\) とすると、AVL 木の任意のノードの平衡係数は常に \(-1 \le f \le 1\) を満たします。

      -

      7.5.2   AVL木の回転

      -

      AVL木の特徴的な機能は「回転」操作で、これは二分木の中順探索シーケンスに影響を与えることなく、不平衡なノードのバランスを回復できます。つまり、回転操作は「二分探索木」の性質を維持しながら、木を「平衡二分木」に戻すことができます

      -

      絶対平衡因子が\(> 1\)のノードを「不平衡ノード」と呼びます。不平衡のタイプに応じて、4種類の回転があります:右回転、左回転、右左回転、左右回転です。以下、これらの回転操作について詳しく説明します。

      +

      7.5.2   AVL 木の回転

      +

      AVL 木の特徴は「回転」操作にあり、二分木の中順走査列を変えずに、不平衡ノードを再び平衡に戻せます。言い換えると、回転操作は「二分探索木」の性質を保ちながら、木を再び「平衡二分木」に戻すことができます

      +

      平衡係数の絶対値が \(> 1\) のノードを「不平衡ノード」と呼びます。ノードの不平衡の形に応じて、回転操作は 4 種類に分かれます。右回転、左回転、右回転してから左回転、左回転してから右回転です。以下でこれらを順に説明します。

      1.   右回転

      -

      下図に示すように、二分木で下から上への最初の不平衡ノードは「ノード3」です。この不平衡ノードを根とする部分木に焦点を当て、これをnodeとし、その左の子をchildとして、「右回転」を実行します。右回転後、部分木は再びバランスが取れ、同時に二分探索木の性質も維持されます。

      +

      以下の図では、ノードの下に平衡係数を示しています。下から上へ見ると、二分木で最初に不平衡になるのは「ノード 3」です。この不平衡ノードを根とする部分木に注目し、そのノードを node、左の子ノードを child として、「右回転」を行います。右回転後、部分木は平衡を回復し、なおかつ二分探索木の性質も保たれます。

      @@ -5056,263 +5245,515 @@

      図 7-26   右回転の手順

      -

      下図に示すように、childノードに右の子(grand_childと表記)がある場合、右回転で手順を追加する必要があります:grand_childnodeの左の子に設定します。

      -

      grand_childがある右回転

      -

      図 7-27   grand_childがある右回転

      +

      以下の図に示すように、ノード child に右の子ノード(grand_child と記す)がある場合、右回転には 1 ステップ追加する必要があります。すなわち、grand_childnode の左の子ノードにします。

      +

      grand_child を持つ右回転

      +

      図 7-27   grand_child を持つ右回転

      -

      「右回転」は比喩的な用語で、実際にはノードポインタを変更することで実現されます。以下のコードで示されます:

      +

      「右に回転する」というのはあくまでイメージしやすい表現であり、実際にはノードポインタを変更して実現します。コードは次のとおりです:

      avl_tree.py
      def right_rotate(self, node: TreeNode | None) -> TreeNode | None:
      -    """右回転操作"""
      +    """右回転"""
           child = node.left
           grand_child = child.right
      -    # childを中心にnodeを右に回転
      +    # child を支点として node を右回転させる
           child.right = node
           node.left = grand_child
      -    # ノードの高さを更新
      +    # ノードの高さを更新する
           self.update_height(node)
           self.update_height(child)
      -    # 回転後の部分木のルートを返す
      +    # 回転後の部分木の根ノードを返す
           return child
       
      -
      avl_tree.cpp
      /* 右回転操作 */
      +
      avl_tree.cpp
      /* 右回転 */
       TreeNode *rightRotate(TreeNode *node) {
           TreeNode *child = node->left;
           TreeNode *grandChild = child->right;
      -    // childを中心にnodeを右に回転
      +    // child を支点として node を右回転させる
           child->right = node;
           node->left = grandChild;
      -    // ノードの高さを更新
      +    // ノードの高さを更新する
           updateHeight(node);
           updateHeight(child);
      -    // 回転後の部分木のルートを返す
      +    // 回転後の部分木の根ノードを返す
           return child;
       }
       
      -
      avl_tree.java
      /* 右回転操作 */
      +
      avl_tree.java
      /* 右回転 */
       TreeNode rightRotate(TreeNode node) {
           TreeNode child = node.left;
           TreeNode grandChild = child.right;
      -    // child を軸として node を右に回転
      +    // child を支点として node を右回転させる
           child.right = node;
           node.left = grandChild;
      -    // ノードの高さを更新
      +    // ノードの高さを更新する
           updateHeight(node);
           updateHeight(child);
      -    // 回転後の部分木の根を返す
      +    // 回転後の部分木の根ノードを返す
           return child;
       }
       
      -
      avl_tree.cs
      [class]{AVLTree}-[func]{RightRotate}
      +
      avl_tree.cs
      /* 右回転 */
      +TreeNode? RightRotate(TreeNode? node) {
      +    TreeNode? child = node?.left;
      +    TreeNode? grandChild = child?.right;
      +    // child を支点として node を右回転させる
      +    child.right = node;
      +    node.left = grandChild;
      +    // ノードの高さを更新する
      +    UpdateHeight(node);
      +    UpdateHeight(child);
      +    // 回転後の部分木の根ノードを返す
      +    return child;
      +}
       
      -
      avl_tree.go
      [class]{aVLTree}-[func]{rightRotate}
      +
      avl_tree.go
      /* 右回転 */
      +func (t *aVLTree) rightRotate(node *TreeNode) *TreeNode {
      +    child := node.Left
      +    grandChild := child.Right
      +    // child を支点として node を右回転させる
      +    child.Right = node
      +    node.Left = grandChild
      +    // ノードの高さを更新する
      +    t.updateHeight(node)
      +    t.updateHeight(child)
      +    // 回転後の部分木の根ノードを返す
      +    return child
      +}
       
      -
      avl_tree.swift
      [class]{AVLTree}-[func]{rightRotate}
      +
      avl_tree.swift
      /* 右回転 */
      +func rightRotate(node: TreeNode?) -> TreeNode? {
      +    let child = node?.left
      +    let grandChild = child?.right
      +    // child を支点として node を右回転させる
      +    child?.right = node
      +    node?.left = grandChild
      +    // ノードの高さを更新する
      +    updateHeight(node: node)
      +    updateHeight(node: child)
      +    // 回転後の部分木の根ノードを返す
      +    return child
      +}
       
      -
      avl_tree.js
      [class]{AVLTree}-[func]{rightRotate}
      +
      avl_tree.js
      /* 右回転 */
      +#rightRotate(node) {
      +    const child = node.left;
      +    const grandChild = child.right;
      +    // child を支点として node を右回転させる
      +    child.right = node;
      +    node.left = grandChild;
      +    // ノードの高さを更新する
      +    this.#updateHeight(node);
      +    this.#updateHeight(child);
      +    // 回転後の部分木の根ノードを返す
      +    return child;
      +}
       
      -
      avl_tree.ts
      [class]{AVLTree}-[func]{rightRotate}
      +
      avl_tree.ts
      /* 右回転 */
      +rightRotate(node: TreeNode): TreeNode {
      +    const child = node.left;
      +    const grandChild = child.right;
      +    // child を支点として node を右回転させる
      +    child.right = node;
      +    node.left = grandChild;
      +    // ノードの高さを更新する
      +    this.updateHeight(node);
      +    this.updateHeight(child);
      +    // 回転後の部分木の根ノードを返す
      +    return child;
      +}
       
      -
      avl_tree.dart
      [class]{AVLTree}-[func]{rightRotate}
      +
      avl_tree.dart
      /* 右回転 */
      +TreeNode? rightRotate(TreeNode? node) {
      +  TreeNode? child = node!.left;
      +  TreeNode? grandChild = child!.right;
      +  // child を支点として node を右回転させる
      +  child.right = node;
      +  node.left = grandChild;
      +  // ノードの高さを更新する
      +  updateHeight(node);
      +  updateHeight(child);
      +  // 回転後の部分木の根ノードを返す
      +  return child;
      +}
       
      -
      avl_tree.rs
      [class]{AVLTree}-[func]{right_rotate}
      +
      avl_tree.rs
      /* 右回転 */
      +fn right_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc {
      +    match node {
      +        Some(node) => {
      +            let child = node.borrow().left.clone().unwrap();
      +            let grand_child = child.borrow().right.clone();
      +            // child を支点として node を右回転させる
      +            child.borrow_mut().right = Some(node.clone());
      +            node.borrow_mut().left = grand_child;
      +            // ノードの高さを更新する
      +            Self::update_height(Some(node));
      +            Self::update_height(Some(child.clone()));
      +            // 回転後の部分木の根ノードを返す
      +            Some(child)
      +        }
      +        None => None,
      +    }
      +}
       
      -
      avl_tree.c
      [class]{}-[func]{rightRotate}
      +
      avl_tree.c
      /* 右回転 */
      +TreeNode *rightRotate(TreeNode *node) {
      +    TreeNode *child, *grandChild;
      +    child = node->left;
      +    grandChild = child->right;
      +    // child を支点として node を右回転させる
      +    child->right = node;
      +    node->left = grandChild;
      +    // ノードの高さを更新する
      +    updateHeight(node);
      +    updateHeight(child);
      +    // 回転後の部分木の根ノードを返す
      +    return child;
      +}
       
      -
      avl_tree.kt
      [class]{AVLTree}-[func]{rightRotate}
      +
      avl_tree.kt
      /* 右回転 */
      +fun rightRotate(node: TreeNode?): TreeNode {
      +    val child = node!!.left
      +    val grandChild = child!!.right
      +    // child を支点として node を右回転させる
      +    child.right = node
      +    node.left = grandChild
      +    // ノードの高さを更新する
      +    updateHeight(node)
      +    updateHeight(child)
      +    // 回転後の部分木の根ノードを返す
      +    return child
      +}
       
      -
      avl_tree.rb
      [class]{AVLTree}-[func]{right_rotate}
      +
      avl_tree.rb
      ### 右回転操作 ###
      +def right_rotate(node)
      +  child = node.left
      +  grand_child = child.right
      +  # child を支点として node を右回転させる
      +  child.right = node
      +  node.left = grand_child
      +  # ノードの高さを更新する
      +  update_height(node)
      +  update_height(child)
      +  # 回転後の部分木の根ノードを返す
      +  child
      +end
       

      2.   左回転

      -

      対応して、上記の不平衡二分木の「鏡像」を考慮すると、下図に示す「左回転」操作を実行する必要があります。

      -

      左回転操作

      -

      図 7-28   左回転操作

      +

      対応する鏡像として、上記の不平衡二分木を左右反転して考えると、以下の図に示す「左回転」が必要になります。

      +

      左回転

      +

      図 7-28   左回転

      -

      同様に、下図に示すように、childノードに左の子(grand_childと表記)がある場合、左回転で手順を追加する必要があります:grand_childnodeの右の子に設定します。

      -

      grand_childがある左回転

      -

      図 7-29   grand_childがある左回転

      +

      同様に、以下の図に示すように、ノード child に左の子ノード(grand_child と記す)がある場合、左回転にも 1 ステップ追加する必要があります。すなわち、grand_childnode の右の子ノードにします。

      +

      grand_child を持つ左回転

      +

      図 7-29   grand_child を持つ左回転

      -

      **右回転と左回転の操作は論理的に対称であり、2つの対称的な不平衡タイプを解決します**ことが観察できます。対称性に基づいて、右回転の実装コードですべてのleftrightに、すべてのrightleftに置き換えることで、左回転の実装コードを得ることができます:

      +

      分かるように、右回転と左回転は論理的に鏡像対称であり、それぞれが解決する 2 種類の不平衡も対称です。この対称性に基づけば、右回転の実装コードにあるすべての leftright に、すべての rightleft に置き換えるだけで、左回転の実装コードが得られます:

      avl_tree.py
      def left_rotate(self, node: TreeNode | None) -> TreeNode | None:
      -    """左回転操作"""
      +    """左回転"""
           child = node.right
           grand_child = child.left
      -    # childを中心にnodeを左に回転
      +    # child を支点として node を左回転させる
           child.left = node
           node.right = grand_child
      -    # ノードの高さを更新
      +    # ノードの高さを更新する
           self.update_height(node)
           self.update_height(child)
      -    # 回転後の部分木のルートを返す
      +    # 回転後の部分木の根ノードを返す
           return child
       
      -
      avl_tree.cpp
      /* 左回転操作 */
      +
      avl_tree.cpp
      /* 左回転 */
       TreeNode *leftRotate(TreeNode *node) {
           TreeNode *child = node->right;
           TreeNode *grandChild = child->left;
      -    // childを中心にnodeを左に回転
      +    // child を支点として node を左回転させる
           child->left = node;
           node->right = grandChild;
      -    // ノードの高さを更新
      +    // ノードの高さを更新する
           updateHeight(node);
           updateHeight(child);
      -    // 回転後の部分木のルートを返す
      +    // 回転後の部分木の根ノードを返す
           return child;
       }
       
      -
      avl_tree.java
      /* 左回転操作 */
      +
      avl_tree.java
      /* 左回転 */
       TreeNode leftRotate(TreeNode node) {
           TreeNode child = node.right;
           TreeNode grandChild = child.left;
      -    // child を軸として node を左に回転
      +    // child を支点として node を左回転させる
           child.left = node;
           node.right = grandChild;
      -    // ノードの高さを更新
      +    // ノードの高さを更新する
           updateHeight(node);
           updateHeight(child);
      -    // 回転後の部分木の根を返す
      +    // 回転後の部分木の根ノードを返す
           return child;
       }
       
      -
      avl_tree.cs
      [class]{AVLTree}-[func]{LeftRotate}
      +
      avl_tree.cs
      /* 左回転 */
      +TreeNode? LeftRotate(TreeNode? node) {
      +    TreeNode? child = node?.right;
      +    TreeNode? grandChild = child?.left;
      +    // child を支点として node を左回転させる
      +    child.left = node;
      +    node.right = grandChild;
      +    // ノードの高さを更新する
      +    UpdateHeight(node);
      +    UpdateHeight(child);
      +    // 回転後の部分木の根ノードを返す
      +    return child;
      +}
       
      -
      avl_tree.go
      [class]{aVLTree}-[func]{leftRotate}
      +
      avl_tree.go
      /* 左回転 */
      +func (t *aVLTree) leftRotate(node *TreeNode) *TreeNode {
      +    child := node.Right
      +    grandChild := child.Left
      +    // child を支点として node を左回転させる
      +    child.Left = node
      +    node.Right = grandChild
      +    // ノードの高さを更新する
      +    t.updateHeight(node)
      +    t.updateHeight(child)
      +    // 回転後の部分木の根ノードを返す
      +    return child
      +}
       
      -
      avl_tree.swift
      [class]{AVLTree}-[func]{leftRotate}
      +
      avl_tree.swift
      /* 左回転 */
      +func leftRotate(node: TreeNode?) -> TreeNode? {
      +    let child = node?.right
      +    let grandChild = child?.left
      +    // child を支点として node を左回転させる
      +    child?.left = node
      +    node?.right = grandChild
      +    // ノードの高さを更新する
      +    updateHeight(node: node)
      +    updateHeight(node: child)
      +    // 回転後の部分木の根ノードを返す
      +    return child
      +}
       
      -
      avl_tree.js
      [class]{AVLTree}-[func]{leftRotate}
      +
      avl_tree.js
      /* 左回転 */
      +#leftRotate(node) {
      +    const child = node.right;
      +    const grandChild = child.left;
      +    // child を支点として node を左回転させる
      +    child.left = node;
      +    node.right = grandChild;
      +    // ノードの高さを更新する
      +    this.#updateHeight(node);
      +    this.#updateHeight(child);
      +    // 回転後の部分木の根ノードを返す
      +    return child;
      +}
       
      -
      avl_tree.ts
      [class]{AVLTree}-[func]{leftRotate}
      +
      avl_tree.ts
      /* 左回転 */
      +leftRotate(node: TreeNode): TreeNode {
      +    const child = node.right;
      +    const grandChild = child.left;
      +    // child を支点として node を左回転させる
      +    child.left = node;
      +    node.right = grandChild;
      +    // ノードの高さを更新する
      +    this.updateHeight(node);
      +    this.updateHeight(child);
      +    // 回転後の部分木の根ノードを返す
      +    return child;
      +}
       
      -
      avl_tree.dart
      [class]{AVLTree}-[func]{leftRotate}
      +
      avl_tree.dart
      /* 左回転 */
      +TreeNode? leftRotate(TreeNode? node) {
      +  TreeNode? child = node!.right;
      +  TreeNode? grandChild = child!.left;
      +  // child を支点として node を左回転させる
      +  child.left = node;
      +  node.right = grandChild;
      +  // ノードの高さを更新する
      +  updateHeight(node);
      +  updateHeight(child);
      +  // 回転後の部分木の根ノードを返す
      +  return child;
      +}
       
      -
      avl_tree.rs
      [class]{AVLTree}-[func]{left_rotate}
      +
      avl_tree.rs
      /* 左回転 */
      +fn left_rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc {
      +    match node {
      +        Some(node) => {
      +            let child = node.borrow().right.clone().unwrap();
      +            let grand_child = child.borrow().left.clone();
      +            // child を支点として node を左回転させる
      +            child.borrow_mut().left = Some(node.clone());
      +            node.borrow_mut().right = grand_child;
      +            // ノードの高さを更新する
      +            Self::update_height(Some(node));
      +            Self::update_height(Some(child.clone()));
      +            // 回転後の部分木の根ノードを返す
      +            Some(child)
      +        }
      +        None => None,
      +    }
      +}
       
      -
      avl_tree.c
      [class]{}-[func]{leftRotate}
      +
      avl_tree.c
      /* 左回転 */
      +TreeNode *leftRotate(TreeNode *node) {
      +    TreeNode *child, *grandChild;
      +    child = node->right;
      +    grandChild = child->left;
      +    // child を支点として node を左回転させる
      +    child->left = node;
      +    node->right = grandChild;
      +    // ノードの高さを更新する
      +    updateHeight(node);
      +    updateHeight(child);
      +    // 回転後の部分木の根ノードを返す
      +    return child;
      +}
       
      -
      avl_tree.kt
      [class]{AVLTree}-[func]{leftRotate}
      +
      avl_tree.kt
      /* 左回転 */
      +fun leftRotate(node: TreeNode?): TreeNode {
      +    val child = node!!.right
      +    val grandChild = child!!.left
      +    // child を支点として node を左回転させる
      +    child.left = node
      +    node.right = grandChild
      +    // ノードの高さを更新する
      +    updateHeight(node)
      +    updateHeight(child)
      +    // 回転後の部分木の根ノードを返す
      +    return child
      +}
       
      -
      avl_tree.rb
      [class]{AVLTree}-[func]{left_rotate}
      +
      avl_tree.rb
      ### 左回転操作 ###
      +def left_rotate(node)
      +  child = node.right
      +  grand_child = child.left
      +  # child を支点として node を左回転させる
      +  child.left = node
      +  node.right = grand_child
      +  # ノードの高さを更新する
      +  update_height(node)
      +  update_height(child)
      +  # 回転後の部分木の根ノードを返す
      +  child
      +end
       
      -

      3.   左右回転

      -

      下図に示す不平衡ノード3の場合、左回転または右回転のいずれかだけでは部分木のバランスを回復できません。この場合、まずchildに対して「左回転」を実行し、次にnodeに対して「右回転」を実行する必要があります。

      -

      左右回転

      -

      図 7-30   左右回転

      +

      3.   左回転してから右回転

      +

      以下の図の不平衡ノード 3 では、左回転だけでも右回転だけでも部分木を平衡に戻せません。この場合は、まず child に「左回転」を行い、次に node に「右回転」を行います。

      +

      左回転してから右回転

      +

      図 7-30   左回転してから右回転

      -

      4.   右左回転

      -

      下図に示すように、上記の不平衡二分木の鏡像ケースでは、まずchildに対して「右回転」を実行し、次にnodeに対して「左回転」を実行する必要があります。

      -

      右左回転

      -

      図 7-31   右左回転

      +

      4.   右回転してから左回転

      +

      以下の図に示すように、上記の不平衡二分木の鏡像のケースでは、まず child に「右回転」を行い、次に node に「左回転」を行います。

      +

      右回転してから左回転

      +

      図 7-31   右回転してから左回転

      5.   回転の選択

      -

      下図に示す4種類の不平衡は、それぞれ上記で説明したケースに対応し、右回転、左右回転、右左回転、左回転が必要です。

      -

      AVL木の4つの回転ケース

      -

      図 7-32   AVL木の4つの回転ケース

      +

      以下の図に示す 4 種類の不平衡は、上の各ケースにそれぞれ対応しており、必要な操作は順に右回転、左回転してから右回転、右回転してから左回転、左回転です。

      +

      AVL 木の 4 つの回転ケース

      +

      図 7-32   AVL 木の 4 つの回転ケース

      -

      下表に示すように、不平衡ノードの平衡因子とその高い側の子の平衡因子の符号を判断することで、不平衡ノードが上記のどのケースに属するかを決定します。

      -

      表 7-3   4つの回転ケースの選択条件

      +

      以下の表に示すように、不平衡ノードの平衡係数と、高い側の子ノードの平衡係数の符号を判定することで、その不平衡ノードが上図のどのケースに属するかを判断できます。

      +

      表 7-3   4 種類の回転ケースの選択条件

      - - - + + + - + - + - + - +
      不平衡ノードの平衡因子子ノードの平衡因子使用する回転方法不平衡ノードの平衡係数子ノードの平衡係数採用すべき回転方法
      \(> 1\)(左に傾いた木)\(> 1\) (左に偏った木) \(\geq 0\) 右回転
      \(> 1\)(左に傾いた木)\(> 1\) (左に偏った木) \(<0\) 左回転してから右回転
      \(< -1\)(右に傾いた木)\(< -1\) (右に偏った木) \(\leq 0\) 左回転
      \(< -1\)(右に傾いた木)\(< -1\) (右に偏った木) \(>0\) 右回転してから左回転
      -

      便宜上、回転操作を関数にカプセル化します。この関数により、さまざまな種類の不平衡に対して回転を実行し、不平衡ノードのバランスを回復できます。コードは以下の通りです:

      +

      使いやすくするために、回転操作を 1 つの関数にカプセル化します。この関数があれば、さまざまな不平衡ケースに対して回転を行い、不平衡ノードを再び平衡に戻せます。コードは次のとおりです:

      avl_tree.py
      def rotate(self, node: TreeNode | None) -> TreeNode | None:
      -    """回転操作を実行して部分木のバランスを復元"""
      -    # nodeのバランス因子を取得
      +    """回転操作を行い、この部分木の平衡を回復する"""
      +    # ノード node の平衡係数を取得
           balance_factor = self.balance_factor(node)
      -    # 左偏り木
      +    # 左に偏った木
           if balance_factor > 1:
               if self.balance_factor(node.left) >= 0:
                   # 右回転
      @@ -5321,7 +5762,7 @@
                   # 左回転してから右回転
                   node.left = self.left_rotate(node.left)
                   return self.right_rotate(node)
      -    # 右偏り木
      +    # 右に偏った木
           elif balance_factor < -1:
               if self.balance_factor(node.right) <= 0:
                   # 左回転
      @@ -5330,119 +5771,404 @@
                   # 右回転してから左回転
                   node.right = self.right_rotate(node.right)
                   return self.left_rotate(node)
      -    # バランスの取れた木、回転不要、戻る
      +    # 平衡木なので回転不要、そのまま返す
           return node
       
      -
      avl_tree.cpp
      /* 回転操作を実行して部分木の平衡を回復 */
      +
      avl_tree.cpp
      /* 回転操作を行い、この部分木の平衡を回復する */
       TreeNode *rotate(TreeNode *node) {
      -    // nodeの平衡因子を取得
      +    // ノード node の平衡係数を取得
           int _balanceFactor = balanceFactor(node);
      -    // 左に傾いた木
      +    // 左に偏った木
           if (_balanceFactor > 1) {
               if (balanceFactor(node->left) >= 0) {
                   // 右回転
                   return rightRotate(node);
               } else {
      -            // 先に左回転、その後右回転
      +            // 左回転してから右回転
                   node->left = leftRotate(node->left);
                   return rightRotate(node);
               }
           }
      -    // 右に傾いた木
      +    // 右に偏った木
           if (_balanceFactor < -1) {
               if (balanceFactor(node->right) <= 0) {
                   // 左回転
                   return leftRotate(node);
               } else {
      -            // 先に右回転、その後左回転
      +            // 右回転してから左回転
                   node->right = rightRotate(node->right);
                   return leftRotate(node);
               }
           }
      -    // 平衡な木、回転不要、そのまま戻る
      +    // 平衡木なので回転不要、そのまま返す
           return node;
       }
       
      -
      avl_tree.java
      /* 回転操作を実行して部分木の平衡を回復 */
      +
      avl_tree.java
      /* 回転操作を行い、この部分木の平衡を回復する */
       TreeNode rotate(TreeNode node) {
      -    // node の平衡因子を取得
      +    // ノード node の平衡係数を取得
           int balanceFactor = balanceFactor(node);
      -    // 左傾斜の木
      +    // 左に偏った木
           if (balanceFactor > 1) {
               if (balanceFactor(node.left) >= 0) {
                   // 右回転
                   return rightRotate(node);
               } else {
      -            // 先に左回転、その後右回転
      +            // 左回転してから右回転
                   node.left = leftRotate(node.left);
                   return rightRotate(node);
               }
           }
      -    // 右傾斜の木
      +    // 右に偏った木
           if (balanceFactor < -1) {
               if (balanceFactor(node.right) <= 0) {
                   // 左回転
                   return leftRotate(node);
               } else {
      -            // 先に右回転、その後左回転
      +            // 右回転してから左回転
                   node.right = rightRotate(node.right);
                   return leftRotate(node);
               }
           }
      -    // 平衡木、回転は不要、戻る
      +    // 平衡木なので回転不要、そのまま返す
           return node;
       }
       
      -
      avl_tree.cs
      [class]{AVLTree}-[func]{Rotate}
      +
      avl_tree.cs
      /* 回転操作を行い、この部分木の平衡を回復する */
      +TreeNode? Rotate(TreeNode? node) {
      +    // ノード node の平衡係数を取得
      +    int balanceFactorInt = BalanceFactor(node);
      +    // 左に偏った木
      +    if (balanceFactorInt > 1) {
      +        if (BalanceFactor(node?.left) >= 0) {
      +            // 右回転
      +            return RightRotate(node);
      +        } else {
      +            // 左回転してから右回転
      +            node!.left = LeftRotate(node!.left);
      +            return RightRotate(node);
      +        }
      +    }
      +    // 右に偏った木
      +    if (balanceFactorInt < -1) {
      +        if (BalanceFactor(node?.right) <= 0) {
      +            // 左回転
      +            return LeftRotate(node);
      +        } else {
      +            // 右回転してから左回転
      +            node!.right = RightRotate(node!.right);
      +            return LeftRotate(node);
      +        }
      +    }
      +    // 平衡木なので回転不要、そのまま返す
      +    return node;
      +}
       
      -
      avl_tree.go
      [class]{aVLTree}-[func]{rotate}
      +
      avl_tree.go
      /* 回転操作を行い、この部分木の平衡を回復する */
      +func (t *aVLTree) rotate(node *TreeNode) *TreeNode {
      +    // ノード `node` の平衡係数を取得する
      +    // Go では短い変数名が推奨されるため、ここで `bf` は `t.balanceFactor` を表す
      +    bf := t.balanceFactor(node)
      +    // 左に偏った木
      +    if bf > 1 {
      +        if t.balanceFactor(node.Left) >= 0 {
      +            // 右回転
      +            return t.rightRotate(node)
      +        } else {
      +            // 左回転してから右回転
      +            node.Left = t.leftRotate(node.Left)
      +            return t.rightRotate(node)
      +        }
      +    }
      +    // 右に偏った木
      +    if bf < -1 {
      +        if t.balanceFactor(node.Right) <= 0 {
      +            // 左回転
      +            return t.leftRotate(node)
      +        } else {
      +            // 右回転してから左回転
      +            node.Right = t.rightRotate(node.Right)
      +            return t.leftRotate(node)
      +        }
      +    }
      +    // 平衡木なので回転不要、そのまま返す
      +    return node
      +}
       
      -
      avl_tree.swift
      [class]{AVLTree}-[func]{rotate}
      +
      avl_tree.swift
      /* 回転操作を行い、この部分木の平衡を回復する */
      +func rotate(node: TreeNode?) -> TreeNode? {
      +    // ノード node の平衡係数を取得
      +    let balanceFactor = balanceFactor(node: node)
      +    // 左に偏った木
      +    if balanceFactor > 1 {
      +        if self.balanceFactor(node: node?.left) >= 0 {
      +            // 右回転
      +            return rightRotate(node: node)
      +        } else {
      +            // 左回転してから右回転
      +            node?.left = leftRotate(node: node?.left)
      +            return rightRotate(node: node)
      +        }
      +    }
      +    // 右に偏った木
      +    if balanceFactor < -1 {
      +        if self.balanceFactor(node: node?.right) <= 0 {
      +            // 左回転
      +            return leftRotate(node: node)
      +        } else {
      +            // 右回転してから左回転
      +            node?.right = rightRotate(node: node?.right)
      +            return leftRotate(node: node)
      +        }
      +    }
      +    // 平衡木なので回転不要、そのまま返す
      +    return node
      +}
       
      -
      avl_tree.js
      [class]{AVLTree}-[func]{rotate}
      +
      avl_tree.js
      /* 回転操作を行い、この部分木の平衡を回復する */
      +#rotate(node) {
      +    // ノード node の平衡係数を取得
      +    const balanceFactor = this.balanceFactor(node);
      +    // 左に偏った木
      +    if (balanceFactor > 1) {
      +        if (this.balanceFactor(node.left) >= 0) {
      +            // 右回転
      +            return this.#rightRotate(node);
      +        } else {
      +            // 左回転してから右回転
      +            node.left = this.#leftRotate(node.left);
      +            return this.#rightRotate(node);
      +        }
      +    }
      +    // 右に偏った木
      +    if (balanceFactor < -1) {
      +        if (this.balanceFactor(node.right) <= 0) {
      +            // 左回転
      +            return this.#leftRotate(node);
      +        } else {
      +            // 右回転してから左回転
      +            node.right = this.#rightRotate(node.right);
      +            return this.#leftRotate(node);
      +        }
      +    }
      +    // 平衡木なので回転不要、そのまま返す
      +    return node;
      +}
       
      -
      avl_tree.ts
      [class]{AVLTree}-[func]{rotate}
      +
      avl_tree.ts
      /* 回転操作を行い、この部分木の平衡を回復する */
      +rotate(node: TreeNode): TreeNode {
      +    // ノード node の平衡係数を取得
      +    const balanceFactor = this.balanceFactor(node);
      +    // 左に偏った木
      +    if (balanceFactor > 1) {
      +        if (this.balanceFactor(node.left) >= 0) {
      +            // 右回転
      +            return this.rightRotate(node);
      +        } else {
      +            // 左回転してから右回転
      +            node.left = this.leftRotate(node.left);
      +            return this.rightRotate(node);
      +        }
      +    }
      +    // 右に偏った木
      +    if (balanceFactor < -1) {
      +        if (this.balanceFactor(node.right) <= 0) {
      +            // 左回転
      +            return this.leftRotate(node);
      +        } else {
      +            // 右回転してから左回転
      +            node.right = this.rightRotate(node.right);
      +            return this.leftRotate(node);
      +        }
      +    }
      +    // 平衡木なので回転不要、そのまま返す
      +    return node;
      +}
       
      -
      avl_tree.dart
      [class]{AVLTree}-[func]{rotate}
      +
      avl_tree.dart
      /* 回転操作を行い、この部分木の平衡を回復する */
      +TreeNode? rotate(TreeNode? node) {
      +  // ノード node の平衡係数を取得
      +  int factor = balanceFactor(node);
      +  // 左に偏った木
      +  if (factor > 1) {
      +    if (balanceFactor(node!.left) >= 0) {
      +      // 右回転
      +      return rightRotate(node);
      +    } else {
      +      // 左回転してから右回転
      +      node.left = leftRotate(node.left);
      +      return rightRotate(node);
      +    }
      +  }
      +  // 右に偏った木
      +  if (factor < -1) {
      +    if (balanceFactor(node!.right) <= 0) {
      +      // 左回転
      +      return leftRotate(node);
      +    } else {
      +      // 右回転してから左回転
      +      node.right = rightRotate(node.right);
      +      return leftRotate(node);
      +    }
      +  }
      +  // 平衡木なので回転不要、そのまま返す
      +  return node;
      +}
       
      -
      avl_tree.rs
      [class]{AVLTree}-[func]{rotate}
      +
      avl_tree.rs
      /* 回転操作を行い、この部分木の平衡を回復する */
      +fn rotate(node: OptionTreeNodeRc) -> OptionTreeNodeRc {
      +    // ノード node の平衡係数を取得
      +    let balance_factor = Self::balance_factor(node.clone());
      +    // 左に偏った木
      +    if balance_factor > 1 {
      +        let node = node.unwrap();
      +        if Self::balance_factor(node.borrow().left.clone()) >= 0 {
      +            // 右回転
      +            Self::right_rotate(Some(node))
      +        } else {
      +            // 左回転してから右回転
      +            let left = node.borrow().left.clone();
      +            node.borrow_mut().left = Self::left_rotate(left);
      +            Self::right_rotate(Some(node))
      +        }
      +    }
      +    // 右に偏った木
      +    else if balance_factor < -1 {
      +        let node = node.unwrap();
      +        if Self::balance_factor(node.borrow().right.clone()) <= 0 {
      +            // 左回転
      +            Self::left_rotate(Some(node))
      +        } else {
      +            // 右回転してから左回転
      +            let right = node.borrow().right.clone();
      +            node.borrow_mut().right = Self::right_rotate(right);
      +            Self::left_rotate(Some(node))
      +        }
      +    } else {
      +        // 平衡木なので回転不要、そのまま返す
      +        node
      +    }
      +}
       
      -
      avl_tree.c
      [class]{}-[func]{rotate}
      +
      avl_tree.c
      /* 回転操作を行い、この部分木の平衡を回復する */
      +TreeNode *rotate(TreeNode *node) {
      +    // ノード node の平衡係数を取得
      +    int bf = balanceFactor(node);
      +    // 左に偏った木
      +    if (bf > 1) {
      +        if (balanceFactor(node->left) >= 0) {
      +            // 右回転
      +            return rightRotate(node);
      +        } else {
      +            // 左回転してから右回転
      +            node->left = leftRotate(node->left);
      +            return rightRotate(node);
      +        }
      +    }
      +    // 右に偏った木
      +    if (bf < -1) {
      +        if (balanceFactor(node->right) <= 0) {
      +            // 左回転
      +            return leftRotate(node);
      +        } else {
      +            // 右回転してから左回転
      +            node->right = rightRotate(node->right);
      +            return leftRotate(node);
      +        }
      +    }
      +    // 平衡木なので回転不要、そのまま返す
      +    return node;
      +}
       
      -
      avl_tree.kt
      [class]{AVLTree}-[func]{rotate}
      +
      avl_tree.kt
      /* 回転操作を行い、この部分木の平衡を回復する */
      +fun rotate(node: TreeNode): TreeNode {
      +    // ノード node の平衡係数を取得
      +    val balanceFactor = balanceFactor(node)
      +    // 左に偏った木
      +    if (balanceFactor > 1) {
      +        if (balanceFactor(node.left) >= 0) {
      +            // 右回転
      +            return rightRotate(node)
      +        } else {
      +            // 左回転してから右回転
      +            node.left = leftRotate(node.left)
      +            return rightRotate(node)
      +        }
      +    }
      +    // 右に偏った木
      +    if (balanceFactor < -1) {
      +        if (balanceFactor(node.right) <= 0) {
      +            // 左回転
      +            return leftRotate(node)
      +        } else {
      +            // 右回転してから左回転
      +            node.right = rightRotate(node.right)
      +            return leftRotate(node)
      +        }
      +    }
      +    // 平衡木なので回転不要、そのまま返す
      +    return node
      +}
       
      -
      avl_tree.rb
      [class]{AVLTree}-[func]{rotate}
      +
      avl_tree.rb
      ### 回転操作を行い、この部分木の平衡を回復する ###
      +def rotate(node)
      +  # ノード node の平衡係数を取得
      +  balance_factor = balance_factor(node)
      +  # 左部分木をたどる
      +  if balance_factor > 1
      +    if balance_factor(node.left) >= 0
      +      # 右回転
      +      return right_rotate(node)
      +    else
      +      # 左回転してから右回転
      +      node.left = left_rotate(node.left)
      +      return right_rotate(node)
      +    end
      +  # 右に偏った木
      +  elsif balance_factor < -1
      +    if balance_factor(node.right) <= 0
      +      # 左回転
      +      return left_rotate(node)
      +    else
      +      # 右回転してから左回転
      +      node.right = right_rotate(node.right)
      +      return left_rotate(node)
      +    end
      +  end
      +  # 平衡木なので回転不要、そのまま返す
      +  node
      +end
       
      -

      7.5.3   AVL木の一般的な操作

      +

      7.5.3   AVL 木の基本操作

      1.   ノードの挿入

      -

      AVL木のノード挿入操作は二分探索木のそれと似ています。唯一の違いは、AVL木でノードを挿入した後、そのノードから根ノードまでのパス上に一連の不平衡ノードが現れる可能性があることです。したがって、このノードから始めて上向きに回転操作を実行し、すべての不平衡ノードのバランスを回復する必要があります。コードは以下の通りです:

      +

      AVL 木のノード挿入は、基本的には二分探索木と同じです。唯一の違いは、AVL 木ではノード挿入後に、そのノードから根ノードまでの経路上に複数の不平衡ノードが現れる可能性があることです。したがって、このノードから開始して、下から上へ回転操作を行い、すべての不平衡ノードを平衡に戻す必要があります。コードは次のとおりです:

      @@ -5451,20 +6177,20 @@ self._root = self.insert_helper(self._root, val) def insert_helper(self, node: TreeNode | None, val: int) -> TreeNode: - """再帰的にノードを挿入(ヘルパーメソッド)""" + """ノードを再帰的に挿入する(補助メソッド)""" if node is None: return TreeNode(val) - # 1. 挿入位置を見つけてノードを挿入 + # 1. 挿入位置を探索してノードを挿入 if val < node.val: node.left = self.insert_helper(node.left, val) elif val > node.val: node.right = self.insert_helper(node.right, val) else: - # 重複ノードは挿入しない、戻る + # 重複ノードは挿入せず、そのまま返す return node - # ノードの高さを更新 + # ノードの高さを更新する self.update_height(node) - # 2. 回転操作を実行して部分木のバランスを復元 + # 2. 回転操作を行い、部分木の平衡を回復する return self.rotate(node)
      @@ -5474,21 +6200,21 @@ root = insertHelper(root, val); } -/* ノードを再帰的に挿入(ヘルパーメソッド) */ +/* ノードを再帰的に挿入する(補助メソッド) */ TreeNode *insertHelper(TreeNode *node, int val) { if (node == nullptr) return new TreeNode(val); - /* 1. 挿入位置を見つけてノードを挿入 */ + /* 1. 挿入位置を探索してノードを挿入 */ if (val < node->val) node->left = insertHelper(node->left, val); else if (val > node->val) node->right = insertHelper(node->right, val); else - return node; // 重複ノードは挿入しない、そのまま戻る - updateHeight(node); // ノードの高さを更新 - /* 2. 回転操作を実行して部分木の平衡を回復 */ + return node; // 重複ノードは挿入せず、そのまま返す + updateHeight(node); // ノードの高さを更新する + /* 2. 回転操作を行い、部分木の平衡を回復する */ node = rotate(node); - // 部分木のルートノードを返す + // 部分木の根ノードを返す return node; }
      @@ -5499,19 +6225,19 @@ root = insertHelper(root, val); } -/* 再帰的にノードを挿入(補助メソッド) */ +/* ノードを再帰的に挿入する(補助メソッド) */ TreeNode insertHelper(TreeNode node, int val) { if (node == null) return new TreeNode(val); - /* 1. 挿入位置を見つけてノードを挿入 */ + /* 1. 挿入位置を探索してノードを挿入 */ if (val < node.val) node.left = insertHelper(node.left, val); else if (val > node.val) node.right = insertHelper(node.right, val); else - return node; // 重複ノードは挿入しない、戻る - updateHeight(node); // ノードの高さを更新 - /* 2. 回転操作を実行して部分木の平衡を回復 */ + return node; // 重複ノードは挿入せず、そのまま返す + updateHeight(node); // ノードの高さを更新する + /* 2. 回転操作を行い、部分木の平衡を回復する */ node = rotate(node); // 部分木の根ノードを返す return node; @@ -5519,69 +6245,287 @@
      -
      avl_tree.cs
      [class]{AVLTree}-[func]{Insert}
      -
      -[class]{AVLTree}-[func]{InsertHelper}
      +
      avl_tree.cs
      /* ノードを挿入 */
      +void Insert(int val) {
      +    root = InsertHelper(root, val);
      +}
      +
      +/* ノードを再帰的に挿入する(補助メソッド) */
      +TreeNode? InsertHelper(TreeNode? node, int val) {
      +    if (node == null) return new TreeNode(val);
      +    /* 1. 挿入位置を探索してノードを挿入 */
      +    if (val < node.val)
      +        node.left = InsertHelper(node.left, val);
      +    else if (val > node.val)
      +        node.right = InsertHelper(node.right, val);
      +    else
      +        return node;     // 重複ノードは挿入せず、そのまま返す
      +    UpdateHeight(node);  // ノードの高さを更新する
      +    /* 2. 回転操作を行い、部分木の平衡を回復する */
      +    node = Rotate(node);
      +    // 部分木の根ノードを返す
      +    return node;
      +}
       
      -
      avl_tree.go
      [class]{aVLTree}-[func]{insert}
      -
      -[class]{aVLTree}-[func]{insertHelper}
      +
      avl_tree.go
      /* ノードを挿入 */
      +func (t *aVLTree) insert(val int) {
      +    t.root = t.insertHelper(t.root, val)
      +}
      +
      +/* ノードを再帰的に挿入する(補助関数) */
      +func (t *aVLTree) insertHelper(node *TreeNode, val int) *TreeNode {
      +    if node == nil {
      +        return NewTreeNode(val)
      +    }
      +    /* 1. 挿入位置を探索してノードを挿入 */
      +    if val < node.Val.(int) {
      +        node.Left = t.insertHelper(node.Left, val)
      +    } else if val > node.Val.(int) {
      +        node.Right = t.insertHelper(node.Right, val)
      +    } else {
      +        // 重複ノードは挿入せず、そのまま返す
      +        return node
      +    }
      +    // ノードの高さを更新する
      +    t.updateHeight(node)
      +    /* 2. 回転操作を行い、部分木の平衡を回復する */
      +    node = t.rotate(node)
      +    // 部分木の根ノードを返す
      +    return node
      +}
       
      -
      avl_tree.swift
      [class]{AVLTree}-[func]{insert}
      -
      -[class]{AVLTree}-[func]{insertHelper}
      +
      avl_tree.swift
      /* ノードを挿入 */
      +func insert(val: Int) {
      +    root = insertHelper(node: root, val: val)
      +}
      +
      +/* ノードを再帰的に挿入する(補助メソッド) */
      +func insertHelper(node: TreeNode?, val: Int) -> TreeNode? {
      +    var node = node
      +    if node == nil {
      +        return TreeNode(x: val)
      +    }
      +    /* 1. 挿入位置を探索してノードを挿入 */
      +    if val < node!.val {
      +        node?.left = insertHelper(node: node?.left, val: val)
      +    } else if val > node!.val {
      +        node?.right = insertHelper(node: node?.right, val: val)
      +    } else {
      +        return node // 重複ノードは挿入せず、そのまま返す
      +    }
      +    updateHeight(node: node) // ノードの高さを更新する
      +    /* 2. 回転操作を行い、部分木の平衡を回復する */
      +    node = rotate(node: node)
      +    // 部分木の根ノードを返す
      +    return node
      +}
       
      -
      avl_tree.js
      [class]{AVLTree}-[func]{insert}
      -
      -[class]{AVLTree}-[func]{insertHelper}
      +
      avl_tree.js
      /* ノードを挿入 */
      +insert(val) {
      +    this.root = this.#insertHelper(this.root, val);
      +}
      +
      +/* ノードを再帰的に挿入する(補助メソッド) */
      +#insertHelper(node, val) {
      +    if (node === null) return new TreeNode(val);
      +    /* 1. 挿入位置を探索してノードを挿入 */
      +    if (val < node.val) node.left = this.#insertHelper(node.left, val);
      +    else if (val > node.val)
      +        node.right = this.#insertHelper(node.right, val);
      +    else return node; // 重複ノードは挿入せず、そのまま返す
      +    this.#updateHeight(node); // ノードの高さを更新する
      +    /* 2. 回転操作を行い、部分木の平衡を回復する */
      +    node = this.#rotate(node);
      +    // 部分木の根ノードを返す
      +    return node;
      +}
       
      -
      avl_tree.ts
      [class]{AVLTree}-[func]{insert}
      -
      -[class]{AVLTree}-[func]{insertHelper}
      +
      avl_tree.ts
      /* ノードを挿入 */
      +insert(val: number): void {
      +    this.root = this.insertHelper(this.root, val);
      +}
      +
      +/* ノードを再帰的に挿入する(補助メソッド) */
      +insertHelper(node: TreeNode, val: number): TreeNode {
      +    if (node === null) return new TreeNode(val);
      +    /* 1. 挿入位置を探索してノードを挿入 */
      +    if (val < node.val) {
      +        node.left = this.insertHelper(node.left, val);
      +    } else if (val > node.val) {
      +        node.right = this.insertHelper(node.right, val);
      +    } else {
      +        return node; // 重複ノードは挿入せず、そのまま返す
      +    }
      +    this.updateHeight(node); // ノードの高さを更新する
      +    /* 2. 回転操作を行い、部分木の平衡を回復する */
      +    node = this.rotate(node);
      +    // 部分木の根ノードを返す
      +    return node;
      +}
       
      -
      avl_tree.dart
      [class]{AVLTree}-[func]{insert}
      -
      -[class]{AVLTree}-[func]{insertHelper}
      +
      avl_tree.dart
      /* ノードを挿入 */
      +void insert(int val) {
      +  root = insertHelper(root, val);
      +}
      +
      +/* ノードを再帰的に挿入する(補助メソッド) */
      +TreeNode? insertHelper(TreeNode? node, int val) {
      +  if (node == null) return TreeNode(val);
      +  /* 1. 挿入位置を探索してノードを挿入 */
      +  if (val < node.val)
      +    node.left = insertHelper(node.left, val);
      +  else if (val > node.val)
      +    node.right = insertHelper(node.right, val);
      +  else
      +    return node; // 重複ノードは挿入せず、そのまま返す
      +  updateHeight(node); // ノードの高さを更新する
      +  /* 2. 回転操作を行い、部分木の平衡を回復する */
      +  node = rotate(node);
      +  // 部分木の根ノードを返す
      +  return node;
      +}
       
      -
      avl_tree.rs
      [class]{AVLTree}-[func]{insert}
      -
      -[class]{AVLTree}-[func]{insert_helper}
      +
      avl_tree.rs
      /* ノードを挿入 */
      +fn insert(&mut self, val: i32) {
      +    self.root = Self::insert_helper(self.root.clone(), val);
      +}
      +
      +/* ノードを再帰的に挿入する(補助メソッド) */
      +fn insert_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc {
      +    match node {
      +        Some(mut node) => {
      +            /* 1. 挿入位置を探索してノードを挿入 */
      +            match {
      +                let node_val = node.borrow().val;
      +                node_val
      +            }
      +            .cmp(&val)
      +            {
      +                Ordering::Greater => {
      +                    let left = node.borrow().left.clone();
      +                    node.borrow_mut().left = Self::insert_helper(left, val);
      +                }
      +                Ordering::Less => {
      +                    let right = node.borrow().right.clone();
      +                    node.borrow_mut().right = Self::insert_helper(right, val);
      +                }
      +                Ordering::Equal => {
      +                    return Some(node); // 重複ノードは挿入せず、そのまま返す
      +                }
      +            }
      +            Self::update_height(Some(node.clone())); // ノードの高さを更新する
      +
      +            /* 2. 回転操作を行い、部分木の平衡を回復する */
      +            node = Self::rotate(Some(node)).unwrap();
      +            // 部分木の根ノードを返す
      +            Some(node)
      +        }
      +        None => Some(TreeNode::new(val)),
      +    }
      +}
       
      -
      avl_tree.c
      [class]{AVLTree}-[func]{insert}
      -
      -[class]{}-[func]{insertHelper}
      +
      avl_tree.c
      /* ノードを挿入 */
      +void insert(AVLTree *tree, int val) {
      +    tree->root = insertHelper(tree->root, val);
      +}
      +
      +/* ノードを再帰的に挿入する(補助関数) */
      +TreeNode *insertHelper(TreeNode *node, int val) {
      +    if (node == NULL) {
      +        return newTreeNode(val);
      +    }
      +    /* 1. 挿入位置を探索してノードを挿入 */
      +    if (val < node->val) {
      +        node->left = insertHelper(node->left, val);
      +    } else if (val > node->val) {
      +        node->right = insertHelper(node->right, val);
      +    } else {
      +        // 重複ノードは挿入せず、そのまま返す
      +        return node;
      +    }
      +    // ノードの高さを更新する
      +    updateHeight(node);
      +    /* 2. 回転操作を行い、部分木の平衡を回復する */
      +    node = rotate(node);
      +    // 部分木の根ノードを返す
      +    return node;
      +}
       
      -
      avl_tree.kt
      [class]{AVLTree}-[func]{insert}
      -
      -[class]{AVLTree}-[func]{insertHelper}
      +
      avl_tree.kt
      /* ノードを挿入 */
      +fun insert(_val: Int) {
      +    root = insertHelper(root, _val)
      +}
      +
      +/* ノードを再帰的に挿入する(補助メソッド) */
      +fun insertHelper(n: TreeNode?, _val: Int): TreeNode {
      +    if (n == null)
      +        return TreeNode(_val)
      +    var node = n
      +    /* 1. 挿入位置を探索してノードを挿入 */
      +    if (_val < node._val)
      +        node.left = insertHelper(node.left, _val)
      +    else if (_val > node._val)
      +        node.right = insertHelper(node.right, _val)
      +    else
      +        return node // 重複ノードは挿入せず、そのまま返す
      +    updateHeight(node) // ノードの高さを更新する
      +    /* 2. 回転操作を行い、部分木の平衡を回復する */
      +    node = rotate(node)
      +    // 部分木の根ノードを返す
      +    return node
      +}
       
      -
      avl_tree.rb
      [class]{AVLTree}-[func]{insert}
      -
      -[class]{AVLTree}-[func]{insert_helper}
      +
      avl_tree.rb
      ### ノードを挿入 ###
      +def insert(val)
      +  @root = insert_helper(@root, val)
      +end
      +
      +### ノードを挿入 ###
      +def insert(val)
      +  @root = insert_helper(@root, val)
      +end
      +
      +# ## ノードを再帰的に挿入(補助メソッド)###
      +def insert_helper(node, val)
      +  return TreeNode.new(val) if node.nil?
      +  # 1. 挿入位置を探索してノードを挿入
      +  if val < node.val
      +    node.left = insert_helper(node.left, val)
      +  elsif val > node.val
      +    node.right = insert_helper(node.right, val)
      +  else
      +    # 重複ノードは挿入せず、そのまま返す
      +    return node
      +  end
      +  # ノードの高さを更新する
      +  update_height(node)
      +  # 2. 回転操作を行い、部分木の平衡を回復する
      +  rotate(node)
      +end
       

      2.   ノードの削除

      -

      同様に、二分探索木でのノード削除方法に基づいて、下から上へ回転操作を実行してすべての不平衡ノードのバランスを回復する必要があります。コードは以下の通りです:

      +

      同様に、二分探索木のノード削除メソッドを土台として、下から上へ回転操作を行い、すべての不平衡ノードを平衡に戻す必要があります。コードは次のとおりです:

      @@ -5590,10 +6534,10 @@ self._root = self.remove_helper(self._root, val) def remove_helper(self, node: TreeNode | None, val: int) -> TreeNode | None: - """再帰的にノードを削除(ヘルパーメソッド)""" + """ノードを再帰的に削除する(補助メソッド)""" if node is None: return None - # 1. ノードを見つけて削除 + # 1. ノードを探索して削除 if val < node.val: node.left = self.remove_helper(node.left, val) elif val > node.val: @@ -5601,22 +6545,22 @@ else: if node.left is None or node.right is None: child = node.left or node.right - # 子ノード数 = 0、ノードを削除して戻る + # 子ノード数 = 0 の場合、node をそのまま削除して返す if child is None: return None - # 子ノード数 = 1、ノードを削除 + # 子ノード数 = 1 の場合、node をそのまま削除する else: node = child else: - # 子ノード数 = 2、中順走査の次のノードを削除し、それで現在のノードを置き換え + # 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える temp = node.right while temp.left is not None: temp = temp.left node.right = self.remove_helper(node.right, temp.val) node.val = temp.val - # ノードの高さを更新 + # ノードの高さを更新する self.update_height(node) - # 2. 回転操作を実行して部分木のバランスを復元 + # 2. 回転操作を行い、部分木の平衡を回復する return self.rotate(node)
      @@ -5626,11 +6570,11 @@ root = removeHelper(root, val); } -/* ノードを再帰的に削除(ヘルパーメソッド) */ +/* ノードを再帰的に削除する(補助メソッド) */ TreeNode *removeHelper(TreeNode *node, int val) { if (node == nullptr) return nullptr; - /* 1. ノードを見つけて削除 */ + /* 1. ノードを探索して削除 */ if (val < node->val) node->left = removeHelper(node->left, val); else if (val > node->val) @@ -5638,18 +6582,18 @@ else { if (node->left == nullptr || node->right == nullptr) { TreeNode *child = node->left != nullptr ? node->left : node->right; - // 子ノード数 = 0、ノードを削除して戻る + // 子ノード数 = 0 の場合、node をそのまま削除して返す if (child == nullptr) { delete node; return nullptr; } - // 子ノード数 = 1、ノードを削除 + // 子ノード数 = 1 の場合、node をそのまま削除する else { delete node; node = child; } } else { - // 子ノード数 = 2、中順走査の次のノードを削除し、現在のノードと置き換える + // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える TreeNode *temp = node->right; while (temp->left != nullptr) { temp = temp->left; @@ -5659,10 +6603,10 @@ node->val = tempVal; } } - updateHeight(node); // ノードの高さを更新 - /* 2. 回転操作を実行して部分木の平衡を回復 */ + updateHeight(node); // ノードの高さを更新する + /* 2. 回転操作を行い、部分木の平衡を回復する */ node = rotate(node); - // 部分木のルートノードを返す + // 部分木の根ノードを返す return node; }
      @@ -5673,11 +6617,11 @@ root = removeHelper(root, val); } -/* 再帰的にノードを削除(補助メソッド) */ +/* ノードを再帰的に削除する(補助メソッド) */ TreeNode removeHelper(TreeNode node, int val) { if (node == null) return null; - /* 1. ノードを見つけて削除 */ + /* 1. ノードを探索して削除 */ if (val < node.val) node.left = removeHelper(node.left, val); else if (val > node.val) @@ -5685,14 +6629,14 @@ else { if (node.left == null || node.right == null) { TreeNode child = node.left != null ? node.left : node.right; - // 子ノード数 = 0、ノードを削除して戻る + // 子ノード数 = 0 の場合、node をそのまま削除して返す if (child == null) return null; - // 子ノード数 = 1、ノードを削除 + // 子ノード数 = 1 の場合、node をそのまま削除する else node = child; } else { - // 子ノード数 = 2、中順走査の次のノードを削除し、現在のノードをそれで置き換える + // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える TreeNode temp = node.right; while (temp.left != null) { temp = temp.left; @@ -5701,8 +6645,8 @@ node.val = temp.val; } } - updateHeight(node); // ノードの高さを更新 - /* 2. 回転操作を実行して部分木の平衡を回復 */ + updateHeight(node); // ノードの高さを更新する + /* 2. 回転操作を行い、部分木の平衡を回復する */ node = rotate(node); // 部分木の根ノードを返す return node; @@ -5710,74 +6654,465 @@
      -
      avl_tree.cs
      [class]{AVLTree}-[func]{Remove}
      -
      -[class]{AVLTree}-[func]{RemoveHelper}
      +
      avl_tree.cs
      /* ノードを削除 */
      +void Remove(int val) {
      +    root = RemoveHelper(root, val);
      +}
      +
      +/* ノードを再帰的に削除する(補助メソッド) */
      +TreeNode? RemoveHelper(TreeNode? node, int val) {
      +    if (node == null) return null;
      +    /* 1. ノードを探索して削除 */
      +    if (val < node.val)
      +        node.left = RemoveHelper(node.left, val);
      +    else if (val > node.val)
      +        node.right = RemoveHelper(node.right, val);
      +    else {
      +        if (node.left == null || node.right == null) {
      +            TreeNode? child = node.left ?? node.right;
      +            // 子ノード数 = 0 の場合、node をそのまま削除して返す
      +            if (child == null)
      +                return null;
      +            // 子ノード数 = 1 の場合、node をそのまま削除する
      +            else
      +                node = child;
      +        } else {
      +            // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える
      +            TreeNode? temp = node.right;
      +            while (temp.left != null) {
      +                temp = temp.left;
      +            }
      +            node.right = RemoveHelper(node.right, temp.val!.Value);
      +            node.val = temp.val;
      +        }
      +    }
      +    UpdateHeight(node);  // ノードの高さを更新する
      +    /* 2. 回転操作を行い、部分木の平衡を回復する */
      +    node = Rotate(node);
      +    // 部分木の根ノードを返す
      +    return node;
      +}
       
      -
      avl_tree.go
      [class]{aVLTree}-[func]{remove}
      -
      -[class]{aVLTree}-[func]{removeHelper}
      +
      avl_tree.go
      /* ノードを削除 */
      +func (t *aVLTree) remove(val int) {
      +    t.root = t.removeHelper(t.root, val)
      +}
      +
      +/* ノードを再帰的に削除する(補助関数) */
      +func (t *aVLTree) removeHelper(node *TreeNode, val int) *TreeNode {
      +    if node == nil {
      +        return nil
      +    }
      +    /* 1. ノードを探索して削除 */
      +    if val < node.Val.(int) {
      +        node.Left = t.removeHelper(node.Left, val)
      +    } else if val > node.Val.(int) {
      +        node.Right = t.removeHelper(node.Right, val)
      +    } else {
      +        if node.Left == nil || node.Right == nil {
      +            child := node.Left
      +            if node.Right != nil {
      +                child = node.Right
      +            }
      +            if child == nil {
      +                // 子ノード数 = 0 の場合、node をそのまま削除して返す
      +                return nil
      +            } else {
      +                // 子ノード数 = 1 の場合、node をそのまま削除する
      +                node = child
      +            }
      +        } else {
      +            // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える
      +            temp := node.Right
      +            for temp.Left != nil {
      +                temp = temp.Left
      +            }
      +            node.Right = t.removeHelper(node.Right, temp.Val.(int))
      +            node.Val = temp.Val
      +        }
      +    }
      +    // ノードの高さを更新する
      +    t.updateHeight(node)
      +    /* 2. 回転操作を行い、部分木の平衡を回復する */
      +    node = t.rotate(node)
      +    // 部分木の根ノードを返す
      +    return node
      +}
       
      -
      avl_tree.swift
      [class]{AVLTree}-[func]{remove}
      -
      -[class]{AVLTree}-[func]{removeHelper}
      +
      avl_tree.swift
      /* ノードを削除 */
      +func remove(val: Int) {
      +    root = removeHelper(node: root, val: val)
      +}
      +
      +/* ノードを再帰的に削除する(補助メソッド) */
      +func removeHelper(node: TreeNode?, val: Int) -> TreeNode? {
      +    var node = node
      +    if node == nil {
      +        return nil
      +    }
      +    /* 1. ノードを探索して削除 */
      +    if val < node!.val {
      +        node?.left = removeHelper(node: node?.left, val: val)
      +    } else if val > node!.val {
      +        node?.right = removeHelper(node: node?.right, val: val)
      +    } else {
      +        if node?.left == nil || node?.right == nil {
      +            let child = node?.left ?? node?.right
      +            // 子ノード数 = 0 の場合、node をそのまま削除して返す
      +            if child == nil {
      +                return nil
      +            }
      +            // 子ノード数 = 1 の場合、node をそのまま削除する
      +            else {
      +                node = child
      +            }
      +        } else {
      +            // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える
      +            var temp = node?.right
      +            while temp?.left != nil {
      +                temp = temp?.left
      +            }
      +            node?.right = removeHelper(node: node?.right, val: temp!.val)
      +            node?.val = temp!.val
      +        }
      +    }
      +    updateHeight(node: node) // ノードの高さを更新する
      +    /* 2. 回転操作を行い、部分木の平衡を回復する */
      +    node = rotate(node: node)
      +    // 部分木の根ノードを返す
      +    return node
      +}
       
      -
      avl_tree.js
      [class]{AVLTree}-[func]{remove}
      -
      -[class]{AVLTree}-[func]{removeHelper}
      +
      avl_tree.js
      /* ノードを削除 */
      +remove(val) {
      +    this.root = this.#removeHelper(this.root, val);
      +}
      +
      +/* ノードを再帰的に削除する(補助メソッド) */
      +#removeHelper(node, val) {
      +    if (node === null) return null;
      +    /* 1. ノードを探索して削除 */
      +    if (val < node.val) node.left = this.#removeHelper(node.left, val);
      +    else if (val > node.val)
      +        node.right = this.#removeHelper(node.right, val);
      +    else {
      +        if (node.left === null || node.right === null) {
      +            const child = node.left !== null ? node.left : node.right;
      +            // 子ノード数 = 0 の場合、node をそのまま削除して返す
      +            if (child === null) return null;
      +            // 子ノード数 = 1 の場合、node をそのまま削除する
      +            else node = child;
      +        } else {
      +            // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える
      +            let temp = node.right;
      +            while (temp.left !== null) {
      +                temp = temp.left;
      +            }
      +            node.right = this.#removeHelper(node.right, temp.val);
      +            node.val = temp.val;
      +        }
      +    }
      +    this.#updateHeight(node); // ノードの高さを更新する
      +    /* 2. 回転操作を行い、部分木の平衡を回復する */
      +    node = this.#rotate(node);
      +    // 部分木の根ノードを返す
      +    return node;
      +}
       
      -
      avl_tree.ts
      [class]{AVLTree}-[func]{remove}
      -
      -[class]{AVLTree}-[func]{removeHelper}
      +
      avl_tree.ts
      /* ノードを削除 */
      +remove(val: number): void {
      +    this.root = this.removeHelper(this.root, val);
      +}
      +
      +/* ノードを再帰的に削除する(補助メソッド) */
      +removeHelper(node: TreeNode, val: number): TreeNode {
      +    if (node === null) return null;
      +    /* 1. ノードを探索して削除 */
      +    if (val < node.val) {
      +        node.left = this.removeHelper(node.left, val);
      +    } else if (val > node.val) {
      +        node.right = this.removeHelper(node.right, val);
      +    } else {
      +        if (node.left === null || node.right === null) {
      +            const child = node.left !== null ? node.left : node.right;
      +            // 子ノード数 = 0 の場合、node をそのまま削除して返す
      +            if (child === null) {
      +                return null;
      +            } else {
      +                // 子ノード数 = 1 の場合、node をそのまま削除する
      +                node = child;
      +            }
      +        } else {
      +            // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える
      +            let temp = node.right;
      +            while (temp.left !== null) {
      +                temp = temp.left;
      +            }
      +            node.right = this.removeHelper(node.right, temp.val);
      +            node.val = temp.val;
      +        }
      +    }
      +    this.updateHeight(node); // ノードの高さを更新する
      +    /* 2. 回転操作を行い、部分木の平衡を回復する */
      +    node = this.rotate(node);
      +    // 部分木の根ノードを返す
      +    return node;
      +}
       
      -
      avl_tree.dart
      [class]{AVLTree}-[func]{remove}
      -
      -[class]{AVLTree}-[func]{removeHelper}
      +
      avl_tree.dart
      /* ノードを削除 */
      +void remove(int val) {
      +  root = removeHelper(root, val);
      +}
      +
      +/* ノードを再帰的に削除する(補助メソッド) */
      +TreeNode? removeHelper(TreeNode? node, int val) {
      +  if (node == null) return null;
      +  /* 1. ノードを探索して削除 */
      +  if (val < node.val)
      +    node.left = removeHelper(node.left, val);
      +  else if (val > node.val)
      +    node.right = removeHelper(node.right, val);
      +  else {
      +    if (node.left == null || node.right == null) {
      +      TreeNode? child = node.left ?? node.right;
      +      // 子ノード数 = 0 の場合、node をそのまま削除して返す
      +      if (child == null)
      +        return null;
      +      // 子ノード数 = 1 の場合、node をそのまま削除する
      +      else
      +        node = child;
      +    } else {
      +      // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える
      +      TreeNode? temp = node.right;
      +      while (temp!.left != null) {
      +        temp = temp.left;
      +      }
      +      node.right = removeHelper(node.right, temp.val);
      +      node.val = temp.val;
      +    }
      +  }
      +  updateHeight(node); // ノードの高さを更新する
      +  /* 2. 回転操作を行い、部分木の平衡を回復する */
      +  node = rotate(node);
      +  // 部分木の根ノードを返す
      +  return node;
      +}
       
      -
      avl_tree.rs
      [class]{AVLTree}-[func]{remove}
      -
      -[class]{AVLTree}-[func]{remove_helper}
      +
      avl_tree.rs
      /* ノードを削除 */
      +fn remove(&self, val: i32) {
      +    Self::remove_helper(self.root.clone(), val);
      +}
      +
      +/* ノードを再帰的に削除する(補助メソッド) */
      +fn remove_helper(node: OptionTreeNodeRc, val: i32) -> OptionTreeNodeRc {
      +    match node {
      +        Some(mut node) => {
      +            /* 1. ノードを探索して削除 */
      +            if val < node.borrow().val {
      +                let left = node.borrow().left.clone();
      +                node.borrow_mut().left = Self::remove_helper(left, val);
      +            } else if val > node.borrow().val {
      +                let right = node.borrow().right.clone();
      +                node.borrow_mut().right = Self::remove_helper(right, val);
      +            } else if node.borrow().left.is_none() || node.borrow().right.is_none() {
      +                let child = if node.borrow().left.is_some() {
      +                    node.borrow().left.clone()
      +                } else {
      +                    node.borrow().right.clone()
      +                };
      +                match child {
      +                    // 子ノード数 = 0 の場合、node をそのまま削除して返す
      +                    None => {
      +                        return None;
      +                    }
      +                    // 子ノード数 = 1 の場合、node をそのまま削除する
      +                    Some(child) => node = child,
      +                }
      +            } else {
      +                // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える
      +                let mut temp = node.borrow().right.clone().unwrap();
      +                loop {
      +                    let temp_left = temp.borrow().left.clone();
      +                    if temp_left.is_none() {
      +                        break;
      +                    }
      +                    temp = temp_left.unwrap();
      +                }
      +                let right = node.borrow().right.clone();
      +                node.borrow_mut().right = Self::remove_helper(right, temp.borrow().val);
      +                node.borrow_mut().val = temp.borrow().val;
      +            }
      +            Self::update_height(Some(node.clone())); // ノードの高さを更新する
      +
      +            /* 2. 回転操作を行い、部分木の平衡を回復する */
      +            node = Self::rotate(Some(node)).unwrap();
      +            // 部分木の根ノードを返す
      +            Some(node)
      +        }
      +        None => None,
      +    }
      +}
       
      -
      avl_tree.c
      [class]{AVLTree}-[func]{removeItem}
      -
      -[class]{}-[func]{removeHelper}
      +
      avl_tree.c
      /* ノードを削除 */
      +// stdio.h を導入しているため、ここでは remove 識別子を使えない
      +void removeItem(AVLTree *tree, int val) {
      +    TreeNode *root = removeHelper(tree->root, val);
      +}
      +
      +/* ノードを再帰的に削除する(補助関数) */
      +TreeNode *removeHelper(TreeNode *node, int val) {
      +    TreeNode *child, *grandChild;
      +    if (node == NULL) {
      +        return NULL;
      +    }
      +    /* 1. ノードを探索して削除 */
      +    if (val < node->val) {
      +        node->left = removeHelper(node->left, val);
      +    } else if (val > node->val) {
      +        node->right = removeHelper(node->right, val);
      +    } else {
      +        if (node->left == NULL || node->right == NULL) {
      +            child = node->left;
      +            if (node->right != NULL) {
      +                child = node->right;
      +            }
      +            // 子ノード数 = 0 の場合、node をそのまま削除して返す
      +            if (child == NULL) {
      +                return NULL;
      +            } else {
      +                // 子ノード数 = 1 の場合、node をそのまま削除する
      +                node = child;
      +            }
      +        } else {
      +            // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える
      +            TreeNode *temp = node->right;
      +            while (temp->left != NULL) {
      +                temp = temp->left;
      +            }
      +            int tempVal = temp->val;
      +            node->right = removeHelper(node->right, temp->val);
      +            node->val = tempVal;
      +        }
      +    }
      +    // ノードの高さを更新する
      +    updateHeight(node);
      +    /* 2. 回転操作を行い、部分木の平衡を回復する */
      +    node = rotate(node);
      +    // 部分木の根ノードを返す
      +    return node;
      +}
       
      -
      avl_tree.kt
      [class]{AVLTree}-[func]{remove}
      -
      -[class]{AVLTree}-[func]{removeHelper}
      +
      avl_tree.kt
      /* ノードを削除 */
      +fun remove(_val: Int) {
      +    root = removeHelper(root, _val)
      +}
      +
      +/* ノードを再帰的に削除する(補助メソッド) */
      +fun removeHelper(n: TreeNode?, _val: Int): TreeNode? {
      +    var node = n ?: return null
      +    /* 1. ノードを探索して削除 */
      +    if (_val < node._val)
      +        node.left = removeHelper(node.left, _val)
      +    else if (_val > node._val)
      +        node.right = removeHelper(node.right, _val)
      +    else {
      +        if (node.left == null || node.right == null) {
      +            val child = if (node.left != null)
      +                node.left
      +            else
      +                node.right
      +            // 子ノード数 = 0 の場合、node をそのまま削除して返す
      +            if (child == null)
      +                return null
      +            // 子ノード数 = 1 の場合、node をそのまま削除する
      +            else
      +                node = child
      +        } else {
      +            // 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える
      +            var temp = node.right
      +            while (temp!!.left != null) {
      +                temp = temp.left
      +            }
      +            node.right = removeHelper(node.right, temp._val)
      +            node._val = temp._val
      +        }
      +    }
      +    updateHeight(node) // ノードの高さを更新する
      +    /* 2. 回転操作を行い、部分木の平衡を回復する */
      +    node = rotate(node)
      +    // 部分木の根ノードを返す
      +    return node
      +}
       
      -
      avl_tree.rb
      [class]{AVLTree}-[func]{remove}
      -
      -[class]{AVLTree}-[func]{remove_helper}
      +
      avl_tree.rb
      ### ノードを削除 ###
      +def remove(val)
      +  @root = remove_helper(@root, val)
      +end
      +
      +### ノードを削除 ###
      +def remove(val)
      +  @root = remove_helper(@root, val)
      +end
      +
      +# ## ノードを再帰的に削除(補助メソッド)###
      +def remove_helper(node, val)
      +  return if node.nil?
      +  # 1. ノードを探索して削除
      +  if val < node.val
      +    node.left = remove_helper(node.left, val)
      +  elsif val > node.val
      +    node.right = remove_helper(node.right, val)
      +  else
      +    if node.left.nil? || node.right.nil?
      +      child = node.left || node.right
      +      # 子ノード数 = 0 の場合、node をそのまま削除して返す
      +      return if child.nil?
      +      # 子ノード数 = 1 の場合、node をそのまま削除する
      +      node = child
      +    else
      +      # 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える
      +      temp = node.right
      +      while !temp.left.nil?
      +        temp = temp.left
      +      end
      +      node.right = remove_helper(node.right, temp.val)
      +      node.val = temp.val
      +    end
      +  end
      +  # ノードの高さを更新する
      +  update_height(node)
      +  # 2. 回転操作を行い、部分木の平衡を回復する
      +  rotate(node)
      +end
       
      -

      3.   ノードの検索

      -

      AVL木でのノード検索操作は二分探索木のそれと一致しており、ここでは詳述しません。

      -

      7.5.4   AVL木の典型的な応用

      +

      3.   ノードの探索

      +

      AVL 木のノード探索操作は二分探索木と同じなので、ここでは繰り返しません。

      +

      7.5.4   AVL 木の代表的な応用

        -
      • 大量のデータの整理と格納に使用され、検索頻度が高く、挿入と削除の頻度が低いシナリオに適しています。
      • -
      • データベースのインデックスシステムの構築に使用されます。
      • -
      • 赤黒木も一般的な平衡二分探索木の一種です。AVL木と比較して、赤黒木はより緩い平衡条件を持ち、ノードの挿入と削除にかかる回転数が少なく、ノードの追加と削除操作の平均効率が高くなります。
      • +
      • 大規模データの整理・格納に用いられ、高頻度の探索と低頻度の追加・削除に適しています。
      • +
      • データベースのインデックスシステムの構築に使われます。
      • +
      • 赤黒木も代表的な平衡二分探索木の一つです。AVL 木と比べると、赤黒木は平衡条件がより緩く、ノードの挿入・削除に必要な回転操作が少ないため、平均的な更新効率はより高くなります。
      diff --git a/ja/chapter_tree/binary_search_tree/index.html b/ja/chapter_tree/binary_search_tree/index.html index dbd80dcff..35ca8d8ad 100644 --- a/ja/chapter_tree/binary_search_tree/index.html +++ b/ja/chapter_tree/binary_search_tree/index.html @@ -6,7 +6,7 @@ - + @@ -117,7 +117,7 @@ - 日本語版審閱者を募集しています!詳細は CONTRIBUTING.md を参照してください。 + 日本語版のレビュアーを募集しています。詳しくは こちら をご覧ください。
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1591,7 +1591,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1613,7 +1613,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1635,7 +1635,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1985,7 +1985,7 @@ - 1.   ノードの検索 + 1.   ノードの探索 @@ -2018,7 +2018,7 @@ - 4.   中順走査は順序付けされている + 4.   中順走査は昇順 @@ -2045,7 +2045,7 @@ - 7.4.3   二分探索木の一般的な応用 + 7.4.3   二分探索木の代表的な応用 @@ -2075,7 +2075,7 @@ - 7.5   AVL木 * + 7.5   AVL 木 * @@ -2238,7 +2238,7 @@ - 8.2   ヒープ構築操作 + 8.2   ヒープ構築 @@ -2624,7 +2624,7 @@ - 10.2   二分探索の挿入点 + 10.2   二分探索の挿入位置 @@ -2680,7 +2680,7 @@ - 10.4   ハッシュ最適化戦略 + 10.4   ハッシュによる最適化戦略 @@ -2708,7 +2708,7 @@ - 10.5   探索アルゴリズムの再認識 + 10.5   探索アルゴリズム再考 @@ -3246,7 +3246,7 @@ - 12.1   分割統治アルゴリズム + 12.1   分割統治法 @@ -3302,7 +3302,7 @@ - 12.3   木の構築問題 + 12.3   二分木の構築問題 @@ -3330,7 +3330,7 @@ - 12.4   ハノイの塔問題 + 12.4   ハノイの塔の問題 @@ -3523,7 +3523,7 @@ - 13.3   部分集合和問題 + 13.3   部分和問題 @@ -3551,7 +3551,7 @@ - 13.4   Nクイーン問題 + 13.4   n クイーン問題 @@ -3692,7 +3692,7 @@ - 14.1   動的計画法の初歩 + 14.1   動的計画法入門 @@ -3720,7 +3720,7 @@ - 14.2   DP 問題の特性 + 14.2   動的計画法の問題特性 @@ -3748,7 +3748,7 @@ - 14.3   DP の解法の考え方 + 14.3   動的計画法の問題解決の考え方 @@ -3776,7 +3776,7 @@ - 14.4   0-1ナップサック問題 + 14.4   0-1 ナップサック問題 @@ -3969,7 +3969,7 @@ - 15.1   貪欲アルゴリズム + 15.1   貪欲法 @@ -4214,7 +4214,7 @@ - 16.2   一緒に創作に参加する + 16.2   一緒に制作に参加しましょう @@ -4372,7 +4372,7 @@ - 1.   ノードの検索 + 1.   ノードの探索 @@ -4405,7 +4405,7 @@ - 4.   中順走査は順序付けされている + 4.   中順走査は昇順 @@ -4432,7 +4432,7 @@ - 7.4.3   二分探索木の一般的な応用 + 7.4.3   二分探索木の代表的な応用 @@ -4477,27 +4477,27 @@

      7.4   二分探索木

      -

      下図に示すように、二分探索木は以下の条件を満たします。

      +

      以下の図に示すように、二分探索木(binary search tree)は次の条件を満たします。

      1. 根ノードについて、左部分木のすべてのノードの値 \(<\) 根ノードの値 \(<\) 右部分木のすべてのノードの値。
      2. -
      3. 任意のノードの左と右の部分木も二分探索木です。つまり、条件1.も満たします。
      4. +
      5. 任意のノードの左部分木と右部分木も二分探索木であり、すなわち条件 1. も満たします。

      二分探索木

      図 7-16   二分探索木

      7.4.1   二分探索木の操作

      -

      二分探索木をクラスBinarySearchTreeとしてカプセル化し、木の根ノードを指すメンバー変数rootを宣言します。

      -

      1.   ノードの検索

      -

      ターゲットノード値numが与えられた場合、二分探索木の性質に従って検索できます。下図に示すように、ノードcurを宣言し、二分木の根ノードrootから開始し、ノード値cur.valnumのサイズを比較するループを行います。

      +

      二分探索木をクラス BinarySearchTree としてカプセル化し、木の根ノードを指すメンバ変数 root を宣言します。

      +

      1.   ノードの探索

      +

      目標ノードの値 num が与えられたら、二分探索木の性質に基づいて探索できます。以下の図に示すように、ノード cur を宣言し、二分木の根ノード root から出発して、ノード値 cur.valnum の大小関係を繰り返し比較します。

        -
      • cur.val < numの場合、ターゲットノードはcurの右部分木にあることを意味するため、cur = cur.rightを実行します。
      • -
      • cur.val > numの場合、ターゲットノードはcurの左部分木にあることを意味するため、cur = cur.leftを実行します。
      • -
      • cur.val = numの場合、ターゲットノードが見つかったことを意味するため、ループを終了してノードを返します。
      • +
      • cur.val < num の場合、目標ノードは cur の右部分木にあるため、cur = cur.right を実行します。
      • +
      • cur.val > num の場合、目標ノードは cur の左部分木にあるため、cur = cur.left を実行します。
      • +
      • cur.val = num の場合、目標ノードが見つかったことを表し、ループを抜けてそのノードを返します。
      -

      二分探索木でのノード検索例

      +

      二分探索木のノード探索例

      bst_search_step2

      @@ -4510,42 +4510,42 @@
      -

      図 7-17   二分探索木でのノード検索例

      +

      図 7-17   二分探索木のノード探索例

      -

      二分探索木での検索操作は二分探索アルゴリズムと同じ原理で動作し、各ラウンドでケースの半分を排除します。ループ数は最大で二分木の高さです。二分木が平衡している場合、\(O(\log n)\)の時間を使用します。コード例は以下の通りです:

      +

      二分探索木の探索操作は二分探索アルゴリズムと同じ原理で動作し、各ラウンドで半分の候補を除外します。ループ回数の上限は二分木の高さであり、二分木が平衡であれば \(O(\log n)\) 時間です。コード例は次のとおりです。

      binary_search_tree.py
      def search(self, num: int) -> TreeNode | None:
           """ノードを探索"""
           cur = self._root
      -    # ループで探索、葉ノードを通過した後にブレーク
      +    # ループで探索し、葉ノードを越えたら抜ける
           while cur is not None:
      -        # ターゲットノードはcurの右部分木にある
      +        # 目標ノードは cur の右部分木にある
               if cur.val < num:
                   cur = cur.right
      -        # ターゲットノードはcurの左部分木にある
      +        # 目標ノードは cur の左部分木にある
               elif cur.val > num:
                   cur = cur.left
      -        # ターゲットノードを発見、ループをブレーク
      +        # 目標ノードが見つかったらループを抜ける
               else:
                   break
           return cur
       
      -
      binary_search_tree.cpp
      /* ノードを検索 */
      +
      binary_search_tree.cpp
      /* ノードを探索 */
       TreeNode *search(int num) {
           TreeNode *cur = root;
      -    // ループで検索、葉ノードを通り過ぎたら終了
      +    // ループで探索し、葉ノードを越えたら抜ける
           while (cur != nullptr) {
      -        // 目標ノードはcurの右部分木にある
      +        // 目標ノードは cur の右部分木にある
               if (cur->val < num)
                   cur = cur->right;
      -        // 目標ノードはcurの左部分木にある
      +        // 目標ノードは cur の左部分木にある
               else if (cur->val > num)
                   cur = cur->left;
      -        // 目標ノードを見つけた、ループを抜ける
      +        // 目標ノードが見つかったらループを抜ける
               else
                   break;
           }
      @@ -4555,102 +4555,278 @@
       
      -
      binary_search_tree.java
      /* ノードを検索 */
      +
      binary_search_tree.java
      /* ノードを探索 */
       TreeNode search(int num) {
           TreeNode cur = root;
      -    // ループで検索、葉ノードを通過後に終了
      +    // ループで探索し、葉ノードを越えたら抜ける
           while (cur != null) {
      -        // 対象ノードは cur の右部分木にある
      +        // 目標ノードは cur の右部分木にある
               if (cur.val < num)
                   cur = cur.right;
      -        // 対象ノードは cur の左部分木にある
      +        // 目標ノードは cur の左部分木にある
               else if (cur.val > num)
                   cur = cur.left;
      -        // 対象ノードを見つけた、ループを終了
      +        // 目標ノードが見つかったらループを抜ける
               else
                   break;
           }
      -    // 対象ノードを返す
      +    // 目標ノードを返す
           return cur;
       }
       
      -
      binary_search_tree.cs
      [class]{BinarySearchTree}-[func]{Search}
      +
      binary_search_tree.cs
      /* ノードを探索 */
      +TreeNode? Search(int num) {
      +    TreeNode? cur = root;
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while (cur != null) {
      +        // 目標ノードは cur の右部分木にある
      +        if (cur.val < num) cur =
      +            cur.right;
      +        // 目標ノードは cur の左部分木にある
      +        else if (cur.val > num)
      +            cur = cur.left;
      +        // 目標ノードが見つかったらループを抜ける
      +        else
      +            break;
      +    }
      +    // 目標ノードを返す
      +    return cur;
      +}
       
      -
      binary_search_tree.go
      [class]{binarySearchTree}-[func]{search}
      +
      binary_search_tree.go
      /* ノードを探索 */
      +func (bst *binarySearchTree) search(num int) *TreeNode {
      +    node := bst.root
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    for node != nil {
      +        if node.Val.(int) < num {
      +            // 目標ノードは cur の右部分木にある
      +            node = node.Right
      +        } else if node.Val.(int) > num {
      +            // 目標ノードは cur の左部分木にある
      +            node = node.Left
      +        } else {
      +            // 目標ノードが見つかったらループを抜ける
      +            break
      +        }
      +    }
      +    // 目標ノードを返す
      +    return node
      +}
       
      -
      binary_search_tree.swift
      [class]{BinarySearchTree}-[func]{search}
      +
      binary_search_tree.swift
      /* ノードを探索 */
      +func search(num: Int) -> TreeNode? {
      +    var cur = root
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while cur != nil {
      +        // 目標ノードは cur の右部分木にある
      +        if cur!.val < num {
      +            cur = cur?.right
      +        }
      +        // 目標ノードは cur の左部分木にある
      +        else if cur!.val > num {
      +            cur = cur?.left
      +        }
      +        // 目標ノードが見つかったらループを抜ける
      +        else {
      +            break
      +        }
      +    }
      +    // 目標ノードを返す
      +    return cur
      +}
       
      -
      binary_search_tree.js
      [class]{BinarySearchTree}-[func]{search}
      +
      binary_search_tree.js
      /* ノードを探索 */
      +search(num) {
      +    let cur = this.root;
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while (cur !== null) {
      +        // 目標ノードは cur の右部分木にある
      +        if (cur.val < num) cur = cur.right;
      +        // 目標ノードは cur の左部分木にある
      +        else if (cur.val > num) cur = cur.left;
      +        // 目標ノードが見つかったらループを抜ける
      +        else break;
      +    }
      +    // 目標ノードを返す
      +    return cur;
      +}
       
      -
      binary_search_tree.ts
      [class]{BinarySearchTree}-[func]{search}
      +
      binary_search_tree.ts
      /* ノードを探索 */
      +search(num: number): TreeNode | null {
      +    let cur = this.root;
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while (cur !== null) {
      +        // 目標ノードは cur の右部分木にある
      +        if (cur.val < num) cur = cur.right;
      +        // 目標ノードは cur の左部分木にある
      +        else if (cur.val > num) cur = cur.left;
      +        // 目標ノードが見つかったらループを抜ける
      +        else break;
      +    }
      +    // 目標ノードを返す
      +    return cur;
      +}
       
      -
      binary_search_tree.dart
      [class]{BinarySearchTree}-[func]{search}
      +
      binary_search_tree.dart
      /* ノードを探索 */
      +TreeNode? search(int _num) {
      +  TreeNode? cur = _root;
      +  // ループで探索し、葉ノードを越えたら抜ける
      +  while (cur != null) {
      +    // 目標ノードは cur の右部分木にある
      +    if (cur.val < _num)
      +      cur = cur.right;
      +    // 目標ノードは cur の左部分木にある
      +    else if (cur.val > _num)
      +      cur = cur.left;
      +    // 目標ノードが見つかったらループを抜ける
      +    else
      +      break;
      +  }
      +  // 目標ノードを返す
      +  return cur;
      +}
       
      -
      binary_search_tree.rs
      [class]{BinarySearchTree}-[func]{search}
      +
      binary_search_tree.rs
      /* ノードを探索 */
      +pub fn search(&self, num: i32) -> OptionTreeNodeRc {
      +    let mut cur = self.root.clone();
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while let Some(node) = cur.clone() {
      +        match num.cmp(&node.borrow().val) {
      +            // 目標ノードは cur の右部分木にある
      +            Ordering::Greater => cur = node.borrow().right.clone(),
      +            // 目標ノードは cur の左部分木にある
      +            Ordering::Less => cur = node.borrow().left.clone(),
      +            // 目標ノードが見つかったらループを抜ける
      +            Ordering::Equal => break,
      +        }
      +    }
      +
      +    // 目標ノードを返す
      +    cur
      +}
       
      -
      binary_search_tree.c
      [class]{BinarySearchTree}-[func]{search}
      +
      binary_search_tree.c
      /* ノードを探索 */
      +TreeNode *search(BinarySearchTree *bst, int num) {
      +    TreeNode *cur = bst->root;
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while (cur != NULL) {
      +        if (cur->val < num) {
      +            // 目標ノードは cur の右部分木にある
      +            cur = cur->right;
      +        } else if (cur->val > num) {
      +            // 目標ノードは cur の左部分木にある
      +            cur = cur->left;
      +        } else {
      +            // 目標ノードが見つかったらループを抜ける
      +            break;
      +        }
      +    }
      +    // 目標ノードを返す
      +    return cur;
      +}
       
      -
      binary_search_tree.kt
      [class]{BinarySearchTree}-[func]{search}
      +
      binary_search_tree.kt
      /* ノードを探索 */
      +fun search(num: Int): TreeNode? {
      +    var cur = root
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while (cur != null) {
      +        // 目標ノードは cur の右部分木にある
      +        cur = if (cur._val < num)
      +            cur.right
      +        // 目標ノードは cur の左部分木にある
      +        else if (cur._val > num)
      +            cur.left
      +        // 目標ノードが見つかったらループを抜ける
      +        else
      +            break
      +    }
      +    // 目標ノードを返す
      +    return cur
      +}
       
      -
      binary_search_tree.rb
      [class]{BinarySearchTree}-[func]{search}
      +
      binary_search_tree.rb
      ### ノードを検索 ###
      +def search(num)
      +  cur = @root
      +
      +  # ループで探索し、葉ノードを越えたら抜ける
      +  while !cur.nil?
      +    # 目標ノードは cur の右部分木にある
      +    if cur.val < num
      +      cur = cur.right
      +    # 目標ノードは cur の左部分木にある
      +    elsif cur.val > num
      +      cur = cur.left
      +    # 目標ノードが見つかったらループを抜ける
      +    else
      +      break
      +    end
      +  end
      +
      +  cur
      +end
       
      +
      +コードの可視化 +

      +

      +

      2.   ノードの挿入

      -

      挿入する要素numが与えられた場合、二分探索木の性質「左部分木 < 根ノード < 右部分木」を維持するため、挿入操作は下図に示すように進行します。

      +

      挿入する要素 num が与えられたとき、二分探索木の「左部分木 < 根ノード < 右部分木」という性質を保つため、挿入操作の流れは以下の図のようになります。

        -
      1. 挿入位置を見つける: 検索操作と同様に、根ノードから開始し、現在のノード値とnumのサイズ関係に従って下向きにループし、葉ノードを通過(Noneに走査)するまで、ループを終了します。
      2. -
      3. この位置にノードを挿入: ノードnumを初期化し、Noneがあった場所に配置します。
      4. +
      5. 挿入位置を探索する:探索操作と同様に、根ノードから出発し、現在のノード値と num の大小関係に基づいて下方向へ探索を繰り返し、葉ノードを越えて(None まで到達して)ループを抜けます。
      6. +
      7. その位置にノードを挿入する:ノード num を初期化し、そのノードを None の位置に置きます。
      -

      二分探索木へのノード挿入

      -

      図 7-18   二分探索木へのノード挿入

      +

      二分探索木にノードを挿入する

      +

      図 7-18   二分探索木にノードを挿入する

      -

      コード実装では、以下の2点に注意してください。

      +

      コード実装では、次の 2 点に注意が必要です。

        -
      • 二分探索木は重複ノードの存在を許可しません。そうでなければ、その定義に違反します。したがって、挿入するノードが既に木に存在する場合、挿入は実行されず、ノードは直接戻ります。
      • -
      • 挿入操作を実行するには、前のループからのノードを保存するためにノードpreを使用する必要があります。このようにして、Noneに走査したときに、その親ノードを取得でき、ノード挿入操作を完了できます。
      • +
      • 二分探索木では重複ノードを許可しません。そうでないと定義に反するためです。したがって、挿入対象のノードが木内にすでに存在する場合は、挿入を行わずそのまま返します。
      • +
      • ノード挿入を実現するために、ノード pre を用いて前回のループのノードを保持する必要があります。これにより、None までたどり着いたときにその親ノードを取得でき、ノード挿入を完了できます。
      binary_search_tree.py
      def insert(self, num: int):
           """ノードを挿入"""
      -    # 木が空の場合、ルートノードを初期化
      +    # 木が空なら、根ノードを初期化する
           if self._root is None:
               self._root = TreeNode(num)
               return
      -    # ループで探索、葉ノードを通過した後にブレーク
      +    # ループで探索し、葉ノードを越えたら抜ける
           cur, pre = self._root, None
           while cur is not None:
      -        # 重複ノードを発見したため、戻る
      +        # 重複ノードが見つかったら、直ちに返す
               if cur.val == num:
                   return
               pre = cur
      -        # 挿入位置はcurの右部分木にある
      +        # 挿入位置は cur の右部分木にある
               if cur.val < num:
                   cur = cur.right
      -        # 挿入位置はcurの左部分木にある
      +        # 挿入位置は cur の左部分木にある
               else:
                   cur = cur.left
           # ノードを挿入
      @@ -4664,22 +4840,22 @@
       
      binary_search_tree.cpp
      /* ノードを挿入 */
       void insert(int num) {
      -    // 木が空の場合、ルートノードを初期化
      +    // 木が空なら、根ノードを初期化する
           if (root == nullptr) {
               root = new TreeNode(num);
               return;
           }
           TreeNode *cur = root, *pre = nullptr;
      -    // ループで検索、葉ノードを通り過ぎたら終了
      +    // ループで探索し、葉ノードを越えたら抜ける
           while (cur != nullptr) {
      -        // 重複ノードを見つけた場合、戻る
      +        // 重複ノードが見つかったら、直ちに返す
               if (cur->val == num)
                   return;
               pre = cur;
      -        // 挿入位置はcurの右部分木にある
      +        // 挿入位置は cur の右部分木にある
               if (cur->val < num)
                   cur = cur->right;
      -        // 挿入位置はcurの左部分木にある
      +        // 挿入位置は cur の左部分木にある
               else
                   cur = cur->left;
           }
      @@ -4695,15 +4871,15 @@
       
      binary_search_tree.java
      /* ノードを挿入 */
       void insert(int num) {
      -    // 木が空の場合、根ノードを初期化
      +    // 木が空なら、根ノードを初期化する
           if (root == null) {
               root = new TreeNode(num);
               return;
           }
           TreeNode cur = root, pre = null;
      -    // ループで検索、葉ノードを通過後に終了
      +    // ループで探索し、葉ノードを越えたら抜ける
           while (cur != null) {
      -        // 重複ノードを見つけた場合、戻る
      +        // 重複ノードが見つかったら、直ちに返す
               if (cur.val == num)
                   return;
               pre = cur;
      @@ -4724,68 +4900,360 @@
       
      -
      binary_search_tree.cs
      [class]{BinarySearchTree}-[func]{Insert}
      +
      binary_search_tree.cs
      /* ノードを挿入 */
      +void Insert(int num) {
      +    // 木が空なら、根ノードを初期化する
      +    if (root == null) {
      +        root = new TreeNode(num);
      +        return;
      +    }
      +    TreeNode? cur = root, pre = null;
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while (cur != null) {
      +        // 重複ノードが見つかったら、直ちに返す
      +        if (cur.val == num)
      +            return;
      +        pre = cur;
      +        // 挿入位置は cur の右部分木にある
      +        if (cur.val < num)
      +            cur = cur.right;
      +        // 挿入位置は cur の左部分木にある
      +        else
      +            cur = cur.left;
      +    }
      +
      +    // ノードを挿入
      +    TreeNode node = new(num);
      +    if (pre != null) {
      +        if (pre.val < num)
      +            pre.right = node;
      +        else
      +            pre.left = node;
      +    }
      +}
       
      -
      binary_search_tree.go
      [class]{binarySearchTree}-[func]{insert}
      +
      binary_search_tree.go
      /* ノードを挿入 */
      +func (bst *binarySearchTree) insert(num int) {
      +    cur := bst.root
      +    // 木が空なら、根ノードを初期化する
      +    if cur == nil {
      +        bst.root = NewTreeNode(num)
      +        return
      +    }
      +    // 挿入対象ノードの直前のノード位置
      +    var pre *TreeNode = nil
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    for cur != nil {
      +        if cur.Val == num {
      +            return
      +        }
      +        pre = cur
      +        if cur.Val.(int) < num {
      +            cur = cur.Right
      +        } else {
      +            cur = cur.Left
      +        }
      +    }
      +    // ノードを挿入
      +    node := NewTreeNode(num)
      +    if pre.Val.(int) < num {
      +        pre.Right = node
      +    } else {
      +        pre.Left = node
      +    }
      +}
       
      -
      binary_search_tree.swift
      [class]{BinarySearchTree}-[func]{insert}
      +
      binary_search_tree.swift
      /* ノードを挿入 */
      +func insert(num: Int) {
      +    // 木が空なら、根ノードを初期化する
      +    if root == nil {
      +        root = TreeNode(x: num)
      +        return
      +    }
      +    var cur = root
      +    var pre: TreeNode?
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while cur != nil {
      +        // 重複ノードが見つかったら、直ちに返す
      +        if cur!.val == num {
      +            return
      +        }
      +        pre = cur
      +        // 挿入位置は cur の右部分木にある
      +        if cur!.val < num {
      +            cur = cur?.right
      +        }
      +        // 挿入位置は cur の左部分木にある
      +        else {
      +            cur = cur?.left
      +        }
      +    }
      +    // ノードを挿入
      +    let node = TreeNode(x: num)
      +    if pre!.val < num {
      +        pre?.right = node
      +    } else {
      +        pre?.left = node
      +    }
      +}
       
      -
      binary_search_tree.js
      [class]{BinarySearchTree}-[func]{insert}
      +
      binary_search_tree.js
      /* ノードを挿入 */
      +insert(num) {
      +    // 木が空なら、根ノードを初期化する
      +    if (this.root === null) {
      +        this.root = new TreeNode(num);
      +        return;
      +    }
      +    let cur = this.root,
      +        pre = null;
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while (cur !== null) {
      +        // 重複ノードが見つかったら、直ちに返す
      +        if (cur.val === num) return;
      +        pre = cur;
      +        // 挿入位置は cur の右部分木にある
      +        if (cur.val < num) cur = cur.right;
      +        // 挿入位置は cur の左部分木にある
      +        else cur = cur.left;
      +    }
      +    // ノードを挿入
      +    const node = new TreeNode(num);
      +    if (pre.val < num) pre.right = node;
      +    else pre.left = node;
      +}
       
      -
      binary_search_tree.ts
      [class]{BinarySearchTree}-[func]{insert}
      +
      binary_search_tree.ts
      /* ノードを挿入 */
      +insert(num: number): void {
      +    // 木が空なら、根ノードを初期化する
      +    if (this.root === null) {
      +        this.root = new TreeNode(num);
      +        return;
      +    }
      +    let cur: TreeNode | null = this.root,
      +        pre: TreeNode | null = null;
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while (cur !== null) {
      +        // 重複ノードが見つかったら、直ちに返す
      +        if (cur.val === num) return;
      +        pre = cur;
      +        // 挿入位置は cur の右部分木にある
      +        if (cur.val < num) cur = cur.right;
      +        // 挿入位置は cur の左部分木にある
      +        else cur = cur.left;
      +    }
      +    // ノードを挿入
      +    const node = new TreeNode(num);
      +    if (pre!.val < num) pre!.right = node;
      +    else pre!.left = node;
      +}
       
      -
      binary_search_tree.dart
      [class]{BinarySearchTree}-[func]{insert}
      +
      binary_search_tree.dart
      /* ノードを挿入 */
      +void insert(int _num) {
      +  // 木が空なら、根ノードを初期化する
      +  if (_root == null) {
      +    _root = TreeNode(_num);
      +    return;
      +  }
      +  TreeNode? cur = _root;
      +  TreeNode? pre = null;
      +  // ループで探索し、葉ノードを越えたら抜ける
      +  while (cur != null) {
      +    // 重複ノードが見つかったら、直ちに返す
      +    if (cur.val == _num) return;
      +    pre = cur;
      +    // 挿入位置は cur の右部分木にある
      +    if (cur.val < _num)
      +      cur = cur.right;
      +    // 挿入位置は cur の左部分木にある
      +    else
      +      cur = cur.left;
      +  }
      +  // ノードを挿入
      +  TreeNode? node = TreeNode(_num);
      +  if (pre!.val < _num)
      +    pre.right = node;
      +  else
      +    pre.left = node;
      +}
       
      -
      binary_search_tree.rs
      [class]{BinarySearchTree}-[func]{insert}
      +
      binary_search_tree.rs
      /* ノードを挿入 */
      +pub fn insert(&mut self, num: i32) {
      +    // 木が空なら、根ノードを初期化する
      +    if self.root.is_none() {
      +        self.root = Some(TreeNode::new(num));
      +        return;
      +    }
      +    let mut cur = self.root.clone();
      +    let mut pre = None;
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while let Some(node) = cur.clone() {
      +        match num.cmp(&node.borrow().val) {
      +            // 重複ノードが見つかったら、直ちに返す
      +            Ordering::Equal => return,
      +            // 挿入位置は cur の右部分木にある
      +            Ordering::Greater => {
      +                pre = cur.clone();
      +                cur = node.borrow().right.clone();
      +            }
      +            // 挿入位置は cur の左部分木にある
      +            Ordering::Less => {
      +                pre = cur.clone();
      +                cur = node.borrow().left.clone();
      +            }
      +        }
      +    }
      +    // ノードを挿入
      +    let pre = pre.unwrap();
      +    let node = Some(TreeNode::new(num));
      +    if num > pre.borrow().val {
      +        pre.borrow_mut().right = node;
      +    } else {
      +        pre.borrow_mut().left = node;
      +    }
      +}
       
      -
      binary_search_tree.c
      [class]{BinarySearchTree}-[func]{insert}
      +
      binary_search_tree.c
      /* ノードを挿入 */
      +void insert(BinarySearchTree *bst, int num) {
      +    // 木が空なら、根ノードを初期化する
      +    if (bst->root == NULL) {
      +        bst->root = newTreeNode(num);
      +        return;
      +    }
      +    TreeNode *cur = bst->root, *pre = NULL;
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while (cur != NULL) {
      +        // 重複ノードが見つかったら、直ちに返す
      +        if (cur->val == num) {
      +            return;
      +        }
      +        pre = cur;
      +        if (cur->val < num) {
      +            // 挿入位置は cur の右部分木にある
      +            cur = cur->right;
      +        } else {
      +            // 挿入位置は cur の左部分木にある
      +            cur = cur->left;
      +        }
      +    }
      +    // ノードを挿入
      +    TreeNode *node = newTreeNode(num);
      +    if (pre->val < num) {
      +        pre->right = node;
      +    } else {
      +        pre->left = node;
      +    }
      +}
       
      -
      binary_search_tree.kt
      [class]{BinarySearchTree}-[func]{insert}
      +
      binary_search_tree.kt
      /* ノードを挿入 */
      +fun insert(num: Int) {
      +    // 木が空なら、根ノードを初期化する
      +    if (root == null) {
      +        root = TreeNode(num)
      +        return
      +    }
      +    var cur = root
      +    var pre: TreeNode? = null
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while (cur != null) {
      +        // 重複ノードが見つかったら、直ちに返す
      +        if (cur._val == num)
      +            return
      +        pre = cur
      +        // 挿入位置は cur の右部分木にある
      +        cur = if (cur._val < num)
      +            cur.right
      +        // 挿入位置は cur の左部分木にある
      +        else
      +            cur.left
      +    }
      +    // ノードを挿入
      +    val node = TreeNode(num)
      +    if (pre?._val!! < num)
      +        pre.right = node
      +    else
      +        pre.left = node
      +}
       
      -
      binary_search_tree.rb
      [class]{BinarySearchTree}-[func]{insert}
      +
      binary_search_tree.rb
      ### ノードを挿入 ###
      +def insert(num)
      +  # 木が空なら、根ノードを初期化する
      +  if @root.nil?
      +    @root = TreeNode.new(num)
      +    return
      +  end
      +
      +  # ループで探索し、葉ノードを越えたら抜ける
      +  cur, pre = @root, nil
      +  while !cur.nil?
      +    # 重複ノードが見つかったら、直ちに返す
      +    return if cur.val == num
      +
      +    pre = cur
      +    # 挿入位置は cur の右部分木にある
      +    if cur.val < num
      +      cur = cur.right
      +    # 挿入位置は cur の左部分木にある
      +    else
      +      cur = cur.left
      +    end
      +  end
      +
      +  # ノードを挿入
      +  node = TreeNode.new(num)
      +  if pre.val < num
      +    pre.right = node
      +  else
      +    pre.left = node
      +  end
      +end
       
      -

      ノードの検索と同様に、ノードの挿入には\(O(\log n)\)の時間を使用します。

      +
      +コードの可視化 +

      +

      +
      +

      ノード探索と同様に、ノード挿入には \(O(\log n)\) 時間を要します。

      3.   ノードの削除

      -

      まず、二分木でターゲットノードを見つけ、それを削除します。ノードの挿入と同様に、削除操作が完了した後も、二分探索木の性質「左部分木 < 根ノード < 右部分木」が満たされることを保証する必要があります。したがって、ターゲットノードの子ノード数に基づいて、0、1、2の3つのケースに分け、対応するノード削除操作を実行します。

      -

      下図に示すように、削除するノードの次数が\(0\)の場合、そのノードは葉ノードであることを意味し、直接削除できます。

      -

      二分探索木でのノード削除(次数0)

      -

      図 7-19   二分探索木でのノード削除(次数0)

      +

      まず二分木内で目標ノードを見つけ、その後で削除します。ノード挿入と同様に、削除操作の完了後も二分探索木の「左部分木 < 根ノード < 右部分木」という性質が保たれる必要があります。そのため、目標ノードの子ノード数に応じて、0、1、2 の 3 つのケースに分けて対応する削除操作を行います。

      +

      以下の図に示すように、削除対象ノードの次数が \(0\) のとき、そのノードは葉ノードであり、直接削除できます。

      +

      二分探索木でノードを削除する(次数 0 )

      +

      図 7-19   二分探索木でノードを削除する(次数 0 )

      -

      下図に示すように、削除するノードの次数が\(1\)の場合、削除するノードをその子ノードで置き換えるだけで十分です。

      -

      二分探索木でのノード削除(次数1)

      -

      図 7-20   二分探索木でのノード削除(次数1)

      +

      以下の図に示すように、削除対象ノードの次数が \(1\) のとき、削除対象ノードをその子ノードで置き換えれば十分です。

      +

      二分探索木でノードを削除する(次数 1 )

      +

      図 7-20   二分探索木でノードを削除する(次数 1 )

      -

      削除するノードの次数が\(2\)の場合、直接削除することはできませんが、ノードを使用して置き換える必要があります。二分探索木の性質「左部分木 \(<\) 根ノード \(<\) 右部分木」を維持するため、このノードは右部分木の最小ノードまたは左部分木の最大ノードのいずれかです

      -

      右部分木の最小ノード(中順走査での次のノード)を選択すると仮定すると、削除操作は下図に示すように進行します。

      +

      削除対象ノードの次数が \(2\) のときは、直接削除できず、別のノードでそのノードを置き換える必要があります。二分探索木の「左部分木 \(<\) 根ノード \(<\) 右部分木」という性質を保つ必要があるため、このノードには右部分木の最小ノードまたは左部分木の最大ノードを使えます

      +

      右部分木の最小ノード(中順走査で次のノード)を選ぶと仮定すると、削除操作の流れは以下の図のようになります。

        -
      1. 削除するノードの「中順走査シーケンス」での次のノードを見つけ、tmpとして示します。
      2. -
      3. 削除するノードの値をtmpの値で置き換え、木内でノードtmpを再帰的に削除します。
      4. +
      5. 削除対象ノードの「中順走査列」における次のノードを見つけ、tmp と記します。
      6. +
      7. tmp の値で削除対象ノードの値を上書きし、木の中でノード tmp を再帰的に削除します。
      -

      二分探索木でのノード削除(次数2)

      +

      二分探索木でノードを削除する(次数 2 )

      bst_remove_case3_step2

      @@ -4798,110 +5266,110 @@
      -

      図 7-21   二分探索木でのノード削除(次数2)

      +

      図 7-21   二分探索木でノードを削除する(次数 2 )

      -

      ノードを削除する操作も\(O(\log n)\)の時間を使用します。削除するノードを見つけるのに\(O(\log n)\)の時間が必要で、中順走査の後継ノードを取得するのに\(O(\log n)\)の時間が必要です。コード例は以下の通りです:

      +

      ノード削除操作も同様に \(O(\log n)\) 時間を要します。削除対象ノードの探索に \(O(\log n)\) 時間、中順走査の後続ノードの取得に \(O(\log n)\) 時間が必要です。コード例は次のとおりです。

      binary_search_tree.py
      def remove(self, num: int):
           """ノードを削除"""
      -    # 木が空の場合、戻る
      +    # 木が空なら、そのまま早期リターンする
           if self._root is None:
               return
      -    # ループで探索、葉ノードを通過した後にブレーク
      +    # ループで探索し、葉ノードを越えたら抜ける
           cur, pre = self._root, None
           while cur is not None:
      -        # 削除するノードを発見、ループをブレーク
      +        # 削除対象のノードが見つかったら、ループを抜ける
               if cur.val == num:
                   break
               pre = cur
      -        # 削除するノードはcurの右部分木にある
      +        # 削除対象ノードは cur の右部分木にある
               if cur.val < num:
                   cur = cur.right
      -        # 削除するノードはcurの左部分木にある
      +        # 削除対象ノードは cur の左部分木にある
               else:
                   cur = cur.left
      -    # 削除するノードが存在しない場合、戻る
      +    # 削除対象ノードがなければそのまま返す
           if cur is None:
               return
       
      -    # 子ノード数 = 0 または 1
      +    # 子ノード数 = 0 or 1
           if cur.left is None or cur.right is None:
      -        # 子ノード数 = 0/1の場合、child = null/その子ノード
      +        # 子ノード数が 0 / 1 のとき、child = null / その子ノード
               child = cur.left or cur.right
      -        # ノードcurを削除
      +        # ノード cur を削除する
               if cur != self._root:
                   if pre.left == cur:
                       pre.left = child
                   else:
                       pre.right = child
               else:
      -            # 削除されるノードがルートの場合、ルートを再割り当て
      +            # 削除ノードが根ノードなら、根ノードを再設定
                   self._root = child
           # 子ノード数 = 2
           else:
      -        # curの中順走査の次のノードを取得
      +        # 中順走査における cur の次ノードを取得
               tmp: TreeNode = cur.right
               while tmp.left is not None:
                   tmp = tmp.left
      -        # 再帰的にノードtmpを削除
      +        # ノード tmp を再帰的に削除
               self.remove(tmp.val)
      -        # curをtmpで置き換え
      +        # tmp で cur を上書きする
               cur.val = tmp.val
       
      binary_search_tree.cpp
      /* ノードを削除 */
       void remove(int num) {
      -    // 木が空の場合、戻る
      +    // 木が空なら、そのまま早期リターンする
           if (root == nullptr)
               return;
           TreeNode *cur = root, *pre = nullptr;
      -    // ループで検索、葉ノードを通り過ぎたら終了
      +    // ループで探索し、葉ノードを越えたら抜ける
           while (cur != nullptr) {
      -        // 削除するノードを見つけた、ループを抜ける
      +        // 削除対象のノードが見つかったら、ループを抜ける
               if (cur->val == num)
                   break;
               pre = cur;
      -        // 削除するノードはcurの右部分木にある
      +        // 削除対象ノードは cur の右部分木にある
               if (cur->val < num)
                   cur = cur->right;
      -        // 削除するノードはcurの左部分木にある
      +        // 削除対象ノードは cur の左部分木にある
               else
                   cur = cur->left;
           }
      -    // 削除するノードがない場合、戻る
      +    // 削除対象ノードがなければそのまま返す
           if (cur == nullptr)
               return;
      -    // 子ノード数 = 0 または 1
      +    // 子ノード数 = 0 or 1
           if (cur->left == nullptr || cur->right == nullptr) {
      -        // 子ノード数 = 0 / 1の場合、child = nullptr / その子ノード
      +        // 子ノード数 = 0 / 1 のとき、child = nullptr / その子ノード
               TreeNode *child = cur->left != nullptr ? cur->left : cur->right;
      -        // ノードcurを削除
      +        // ノード cur を削除する
               if (cur != root) {
                   if (pre->left == cur)
                       pre->left = child;
                   else
                       pre->right = child;
               } else {
      -            // 削除されるノードがルートの場合、ルートを再割り当て
      +            // 削除ノードが根ノードなら、根ノードを再設定
                   root = child;
               }
      -        // メモリを解放
      +        // メモリを解放する
               delete cur;
           }
           // 子ノード数 = 2
           else {
      -        // curの中順走査の次のノードを取得
      +        // 中順走査における cur の次ノードを取得
               TreeNode *tmp = cur->right;
               while (tmp->left != nullptr) {
                   tmp = tmp->left;
               }
               int tmpVal = tmp->val;
      -        // ノードtmpを再帰的に削除
      +        // ノード tmp を再帰的に削除
               remove(tmp->val);
      -        // curをtmpで置き換え
      +        // tmp で cur を上書きする
               cur->val = tmpVal;
           }
       }
      @@ -4910,107 +5378,629 @@
       
      binary_search_tree.java
      /* ノードを削除 */
       void remove(int num) {
      -    // 木が空の場合、戻る
      +    // 木が空なら、そのまま早期リターンする
           if (root == null)
               return;
           TreeNode cur = root, pre = null;
      -    // ループで検索、葉ノードを通過後に終了
      +    // ループで探索し、葉ノードを越えたら抜ける
           while (cur != null) {
      -        // 削除するノードを見つけた、ループを終了
      +        // 削除対象のノードが見つかったら、ループを抜ける
               if (cur.val == num)
                   break;
               pre = cur;
      -        // 削除するノードは cur の右部分木にある
      +        // 削除対象ノードは cur の右部分木にある
               if (cur.val < num)
                   cur = cur.right;
      -        // 削除するノードは cur の左部分木にある
      +        // 削除対象ノードは cur の左部分木にある
               else
                   cur = cur.left;
           }
      -    // 削除するノードがない場合、戻る
      +    // 削除対象ノードがなければそのまま返す
           if (cur == null)
               return;
      -    // 子ノード数 = 0 または 1
      +    // 子ノード数 = 0 or 1
           if (cur.left == null || cur.right == null) {
      -        // 子ノード数 = 0/1 の場合、child = null/その子ノード
      +        // 子ノード数が 0 / 1 のとき、child = null / その子ノード
               TreeNode child = cur.left != null ? cur.left : cur.right;
      -        // ノード cur を削除
      +        // ノード cur を削除する
               if (cur != root) {
                   if (pre.left == cur)
                       pre.left = child;
                   else
                       pre.right = child;
               } else {
      -            // 削除されるノードが根の場合、根を再割り当て
      +            // 削除ノードが根ノードなら、根ノードを再設定
                   root = child;
               }
           }
           // 子ノード数 = 2
           else {
      -        // cur の中順走査の次のノードを取得
      +        // 中順走査における cur の次ノードを取得
               TreeNode tmp = cur.right;
               while (tmp.left != null) {
                   tmp = tmp.left;
               }
      -        // 再帰的にノード tmp を削除
      +        // ノード tmp を再帰的に削除
               remove(tmp.val);
      -        // cur を tmp で置き換える
      +        // tmp で cur を上書きする
               cur.val = tmp.val;
           }
       }
       
      -
      binary_search_tree.cs
      [class]{BinarySearchTree}-[func]{Remove}
      +
      binary_search_tree.cs
      /* ノードを削除 */
      +void Remove(int num) {
      +    // 木が空なら、そのまま早期リターンする
      +    if (root == null)
      +        return;
      +    TreeNode? cur = root, pre = null;
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while (cur != null) {
      +        // 削除対象のノードが見つかったら、ループを抜ける
      +        if (cur.val == num)
      +            break;
      +        pre = cur;
      +        // 削除対象ノードは cur の右部分木にある
      +        if (cur.val < num)
      +            cur = cur.right;
      +        // 削除対象ノードは cur の左部分木にある
      +        else
      +            cur = cur.left;
      +    }
      +    // 削除対象ノードがなければそのまま返す
      +    if (cur == null)
      +        return;
      +    // 子ノード数 = 0 or 1
      +    if (cur.left == null || cur.right == null) {
      +        // 子ノード数が 0 / 1 のとき、child = null / その子ノード
      +        TreeNode? child = cur.left ?? cur.right;
      +        // ノード cur を削除する
      +        if (cur != root) {
      +            if (pre!.left == cur)
      +                pre.left = child;
      +            else
      +                pre.right = child;
      +        } else {
      +            // 削除ノードが根ノードなら、根ノードを再設定
      +            root = child;
      +        }
      +    }
      +    // 子ノード数 = 2
      +    else {
      +        // 中順走査における cur の次ノードを取得
      +        TreeNode? tmp = cur.right;
      +        while (tmp.left != null) {
      +            tmp = tmp.left;
      +        }
      +        // ノード tmp を再帰的に削除
      +        Remove(tmp.val!.Value);
      +        // tmp で cur を上書きする
      +        cur.val = tmp.val;
      +    }
      +}
       
      -
      binary_search_tree.go
      [class]{binarySearchTree}-[func]{remove}
      +
      binary_search_tree.go
      /* ノードを削除 */
      +func (bst *binarySearchTree) remove(num int) {
      +    cur := bst.root
      +    // 木が空なら、そのまま早期リターンする
      +    if cur == nil {
      +        return
      +    }
      +    // 削除対象ノードの直前のノード位置
      +    var pre *TreeNode = nil
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    for cur != nil {
      +        if cur.Val == num {
      +            break
      +        }
      +        pre = cur
      +        if cur.Val.(int) < num {
      +            // 削除対象ノードは右部分木にある
      +            cur = cur.Right
      +        } else {
      +            // 削除対象ノードは左部分木にある
      +            cur = cur.Left
      +        }
      +    }
      +    // 削除対象ノードがなければそのまま返す
      +    if cur == nil {
      +        return
      +    }
      +    // 子ノード数は 0 または 1
      +    if cur.Left == nil || cur.Right == nil {
      +        var child *TreeNode = nil
      +        // 削除対象ノードの子ノードを取り出す
      +        if cur.Left != nil {
      +            child = cur.Left
      +        } else {
      +            child = cur.Right
      +        }
      +        // ノード cur を削除する
      +        if cur != bst.root {
      +            if pre.Left == cur {
      +                pre.Left = child
      +            } else {
      +                pre.Right = child
      +            }
      +        } else {
      +            // 削除ノードが根ノードなら、根ノードを再設定
      +            bst.root = child
      +        }
      +        // 子ノード数は 2
      +    } else {
      +        // 中順走査で削除対象ノード `cur` の次のノードを取得する
      +        tmp := cur.Right
      +        for tmp.Left != nil {
      +            tmp = tmp.Left
      +        }
      +        // ノード tmp を再帰的に削除
      +        bst.remove(tmp.Val.(int))
      +        // tmp で cur を上書きする
      +        cur.Val = tmp.Val
      +    }
      +}
       
      -
      binary_search_tree.swift
      [class]{BinarySearchTree}-[func]{remove}
      +
      binary_search_tree.swift
      /* ノードを削除 */
      +func remove(num: Int) {
      +    // 木が空なら、そのまま早期リターンする
      +    if root == nil {
      +        return
      +    }
      +    var cur = root
      +    var pre: TreeNode?
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while cur != nil {
      +        // 削除対象のノードが見つかったら、ループを抜ける
      +        if cur!.val == num {
      +            break
      +        }
      +        pre = cur
      +        // 削除対象ノードは cur の右部分木にある
      +        if cur!.val < num {
      +            cur = cur?.right
      +        }
      +        // 削除対象ノードは cur の左部分木にある
      +        else {
      +            cur = cur?.left
      +        }
      +    }
      +    // 削除対象ノードがなければそのまま返す
      +    if cur == nil {
      +        return
      +    }
      +    // 子ノード数 = 0 or 1
      +    if cur?.left == nil || cur?.right == nil {
      +        // 子ノード数が 0 / 1 のとき、child = null / その子ノード
      +        let child = cur?.left ?? cur?.right
      +        // ノード cur を削除する
      +        if cur !== root {
      +            if pre?.left === cur {
      +                pre?.left = child
      +            } else {
      +                pre?.right = child
      +            }
      +        } else {
      +            // 削除ノードが根ノードなら、根ノードを再設定
      +            root = child
      +        }
      +    }
      +    // 子ノード数 = 2
      +    else {
      +        // 中順走査における cur の次ノードを取得
      +        var tmp = cur?.right
      +        while tmp?.left != nil {
      +            tmp = tmp?.left
      +        }
      +        // ノード tmp を再帰的に削除
      +        remove(num: tmp!.val)
      +        // tmp で cur を上書きする
      +        cur?.val = tmp!.val
      +    }
      +}
       
      -
      binary_search_tree.js
      [class]{BinarySearchTree}-[func]{remove}
      +
      binary_search_tree.js
      /* ノードを削除 */
      +remove(num) {
      +    // 木が空なら、そのまま早期リターンする
      +    if (this.root === null) return;
      +    let cur = this.root,
      +        pre = null;
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while (cur !== null) {
      +        // 削除対象のノードが見つかったら、ループを抜ける
      +        if (cur.val === num) break;
      +        pre = cur;
      +        // 削除対象ノードは cur の右部分木にある
      +        if (cur.val < num) cur = cur.right;
      +        // 削除対象ノードは cur の左部分木にある
      +        else cur = cur.left;
      +    }
      +    // 削除対象ノードがなければそのまま返す
      +    if (cur === null) return;
      +    // 子ノード数 = 0 or 1
      +    if (cur.left === null || cur.right === null) {
      +        // 子ノード数が 0 / 1 のとき、child = null / その子ノード
      +        const child = cur.left !== null ? cur.left : cur.right;
      +        // ノード cur を削除する
      +        if (cur !== this.root) {
      +            if (pre.left === cur) pre.left = child;
      +            else pre.right = child;
      +        } else {
      +            // 削除ノードが根ノードなら、根ノードを再設定
      +            this.root = child;
      +        }
      +    }
      +    // 子ノード数 = 2
      +    else {
      +        // 中順走査における cur の次ノードを取得
      +        let tmp = cur.right;
      +        while (tmp.left !== null) {
      +            tmp = tmp.left;
      +        }
      +        // ノード tmp を再帰的に削除
      +        this.remove(tmp.val);
      +        // tmp で cur を上書きする
      +        cur.val = tmp.val;
      +    }
      +}
       
      -
      binary_search_tree.ts
      [class]{BinarySearchTree}-[func]{remove}
      +
      binary_search_tree.ts
      /* ノードを削除 */
      +remove(num: number): void {
      +    // 木が空なら、そのまま早期リターンする
      +    if (this.root === null) return;
      +    let cur: TreeNode | null = this.root,
      +        pre: TreeNode | null = null;
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while (cur !== null) {
      +        // 削除対象のノードが見つかったら、ループを抜ける
      +        if (cur.val === num) break;
      +        pre = cur;
      +        // 削除対象ノードは cur の右部分木にある
      +        if (cur.val < num) cur = cur.right;
      +        // 削除対象ノードは cur の左部分木にある
      +        else cur = cur.left;
      +    }
      +    // 削除対象ノードがなければそのまま返す
      +    if (cur === null) return;
      +    // 子ノード数 = 0 or 1
      +    if (cur.left === null || cur.right === null) {
      +        // 子ノード数が 0 / 1 のとき、child = null / その子ノード
      +        const child: TreeNode | null =
      +            cur.left !== null ? cur.left : cur.right;
      +        // ノード cur を削除する
      +        if (cur !== this.root) {
      +            if (pre!.left === cur) pre!.left = child;
      +            else pre!.right = child;
      +        } else {
      +            // 削除ノードが根ノードなら、根ノードを再設定
      +            this.root = child;
      +        }
      +    }
      +    // 子ノード数 = 2
      +    else {
      +        // 中順走査における cur の次ノードを取得
      +        let tmp: TreeNode | null = cur.right;
      +        while (tmp!.left !== null) {
      +            tmp = tmp!.left;
      +        }
      +        // ノード tmp を再帰的に削除
      +        this.remove(tmp!.val);
      +        // tmp で cur を上書きする
      +        cur.val = tmp!.val;
      +    }
      +}
       
      -
      binary_search_tree.dart
      [class]{BinarySearchTree}-[func]{remove}
      +
      binary_search_tree.dart
      /* ノードを削除 */
      +void remove(int _num) {
      +  // 木が空なら、そのまま早期リターンする
      +  if (_root == null) return;
      +  TreeNode? cur = _root;
      +  TreeNode? pre = null;
      +  // ループで探索し、葉ノードを越えたら抜ける
      +  while (cur != null) {
      +    // 削除対象のノードが見つかったら、ループを抜ける
      +    if (cur.val == _num) break;
      +    pre = cur;
      +    // 削除対象ノードは cur の右部分木にある
      +    if (cur.val < _num)
      +      cur = cur.right;
      +    // 削除対象ノードは cur の左部分木にある
      +    else
      +      cur = cur.left;
      +  }
      +  // 削除対象ノードがない場合は、そのまま返す
      +  if (cur == null) return;
      +  // 子ノード数 = 0 or 1
      +  if (cur.left == null || cur.right == null) {
      +    // 子ノード数が 0 / 1 のとき、child = null / その子ノード
      +    TreeNode? child = cur.left ?? cur.right;
      +    // ノード cur を削除する
      +    if (cur != _root) {
      +      if (pre!.left == cur)
      +        pre.left = child;
      +      else
      +        pre.right = child;
      +    } else {
      +      // 削除ノードが根ノードなら、根ノードを再設定
      +      _root = child;
      +    }
      +  } else {
      +    // 子ノード数 = 2
      +    // 中順走査における cur の次のノードを取得
      +    TreeNode? tmp = cur.right;
      +    while (tmp!.left != null) {
      +      tmp = tmp.left;
      +    }
      +    // ノード tmp を再帰的に削除
      +    remove(tmp.val);
      +    // tmp で cur を上書きする
      +    cur.val = tmp.val;
      +  }
      +}
       
      -
      binary_search_tree.rs
      [class]{BinarySearchTree}-[func]{remove}
      +
      binary_search_tree.rs
      /* ノードを削除 */
      +pub fn remove(&mut self, num: i32) {
      +    // 木が空なら、そのまま早期リターンする
      +    if self.root.is_none() {
      +        return;
      +    }
      +    let mut cur = self.root.clone();
      +    let mut pre = None;
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while let Some(node) = cur.clone() {
      +        match num.cmp(&node.borrow().val) {
      +            // 削除対象のノードが見つかったら、ループを抜ける
      +            Ordering::Equal => break,
      +            // 削除対象ノードは cur の右部分木にある
      +            Ordering::Greater => {
      +                pre = cur.clone();
      +                cur = node.borrow().right.clone();
      +            }
      +            // 削除対象ノードは cur の左部分木にある
      +            Ordering::Less => {
      +                pre = cur.clone();
      +                cur = node.borrow().left.clone();
      +            }
      +        }
      +    }
      +    // 削除対象ノードがなければそのまま返す
      +    if cur.is_none() {
      +        return;
      +    }
      +    let cur = cur.unwrap();
      +    let (left_child, right_child) = (cur.borrow().left.clone(), cur.borrow().right.clone());
      +    match (left_child.clone(), right_child.clone()) {
      +        // 子ノード数 = 0 or 1
      +        (None, None) | (Some(_), None) | (None, Some(_)) => {
      +            // 子ノード数 = 0 / 1 のとき、child = nullptr / その子ノード
      +            let child = left_child.or(right_child);
      +            let pre = pre.unwrap();
      +            // ノード cur を削除する
      +            if !Rc::ptr_eq(&cur, self.root.as_ref().unwrap()) {
      +                let left = pre.borrow().left.clone();
      +                if left.is_some() && Rc::ptr_eq(left.as_ref().unwrap(), &cur) {
      +                    pre.borrow_mut().left = child;
      +                } else {
      +                    pre.borrow_mut().right = child;
      +                }
      +            } else {
      +                // 削除ノードが根ノードなら、根ノードを再設定
      +                self.root = child;
      +            }
      +        }
      +        // 子ノード数 = 2
      +        (Some(_), Some(_)) => {
      +            // 中順走査における cur の次ノードを取得
      +            let mut tmp = cur.borrow().right.clone();
      +            while let Some(node) = tmp.clone() {
      +                if node.borrow().left.is_some() {
      +                    tmp = node.borrow().left.clone();
      +                } else {
      +                    break;
      +                }
      +            }
      +            let tmp_val = tmp.unwrap().borrow().val;
      +            // ノード tmp を再帰的に削除
      +            self.remove(tmp_val);
      +            // tmp で cur を上書きする
      +            cur.borrow_mut().val = tmp_val;
      +        }
      +    }
      +}
       
      -
      binary_search_tree.c
      [class]{BinarySearchTree}-[func]{removeItem}
      +
      binary_search_tree.c
      /* ノードを削除 */
      +// stdio.h を導入しているため、ここでは remove 識別子を使えない
      +void removeItem(BinarySearchTree *bst, int num) {
      +    // 木が空なら、そのまま早期リターンする
      +    if (bst->root == NULL)
      +        return;
      +    TreeNode *cur = bst->root, *pre = NULL;
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while (cur != NULL) {
      +        // 削除対象のノードが見つかったら、ループを抜ける
      +        if (cur->val == num)
      +            break;
      +        pre = cur;
      +        if (cur->val < num) {
      +            // 削除対象ノードは root の右部分木にある
      +            cur = cur->right;
      +        } else {
      +            // 削除対象ノードは root の左部分木にある
      +            cur = cur->left;
      +        }
      +    }
      +    // 削除対象ノードがなければそのまま返す
      +    if (cur == NULL)
      +        return;
      +    // 削除対象ノードに子ノードがあるかを判定する
      +    if (cur->left == NULL || cur->right == NULL) {
      +        /* 子ノード数 = 0 or 1 */
      +        // 子ノード数 = 0 / 1 のとき、child = nullptr / その子ノード
      +        TreeNode *child = cur->left != NULL ? cur->left : cur->right;
      +        // ノード cur を削除する
      +        if (pre->left == cur) {
      +            pre->left = child;
      +        } else {
      +            pre->right = child;
      +        }
      +        // メモリを解放する
      +        free(cur);
      +    } else {
      +        /* 子ノード数 = 2 */
      +        // 中順走査における cur の次ノードを取得
      +        TreeNode *tmp = cur->right;
      +        while (tmp->left != NULL) {
      +            tmp = tmp->left;
      +        }
      +        int tmpVal = tmp->val;
      +        // ノード tmp を再帰的に削除
      +        removeItem(bst, tmp->val);
      +        // tmp で cur を上書きする
      +        cur->val = tmpVal;
      +    }
      +}
       
      -
      binary_search_tree.kt
      [class]{BinarySearchTree}-[func]{remove}
      +
      binary_search_tree.kt
      /* ノードを削除 */
      +fun remove(num: Int) {
      +    // 木が空なら、そのまま早期リターンする
      +    if (root == null)
      +        return
      +    var cur = root
      +    var pre: TreeNode? = null
      +    // ループで探索し、葉ノードを越えたら抜ける
      +    while (cur != null) {
      +        // 削除対象のノードが見つかったら、ループを抜ける
      +        if (cur._val == num)
      +            break
      +        pre = cur
      +        // 削除対象ノードは cur の右部分木にある
      +        cur = if (cur._val < num)
      +            cur.right
      +        // 削除対象ノードは cur の左部分木にある
      +        else
      +            cur.left
      +    }
      +    // 削除対象ノードがなければそのまま返す
      +    if (cur == null)
      +        return
      +    // 子ノード数 = 0 or 1
      +    if (cur.left == null || cur.right == null) {
      +        // 子ノード数が 0 / 1 のとき、child = null / その子ノード
      +        val child = if (cur.left != null)
      +            cur.left
      +        else
      +            cur.right
      +        // ノード cur を削除する
      +        if (cur != root) {
      +            if (pre!!.left == cur)
      +                pre.left = child
      +            else
      +                pre.right = child
      +        } else {
      +            // 削除ノードが根ノードなら、根ノードを再設定
      +            root = child
      +        }
      +        // 子ノード数 = 2
      +    } else {
      +        // 中順走査における cur の次ノードを取得
      +        var tmp = cur.right
      +        while (tmp!!.left != null) {
      +            tmp = tmp.left
      +        }
      +        // ノード tmp を再帰的に削除
      +        remove(tmp._val)
      +        // tmp で cur を上書きする
      +        cur._val = tmp._val
      +    }
      +}
       
      -
      binary_search_tree.rb
      [class]{BinarySearchTree}-[func]{remove}
      +
      binary_search_tree.rb
      ### ノードを削除 ###
      +def remove(num)
      +  # 木が空なら、そのまま早期リターンする
      +  return if @root.nil?
      +
      +  # ループで探索し、葉ノードを越えたら抜ける
      +  cur, pre = @root, nil
      +  while !cur.nil?
      +    # 削除対象のノードが見つかったら、ループを抜ける
      +    break if cur.val == num
      +
      +    pre = cur
      +    # 削除対象ノードは cur の右部分木にある
      +    if cur.val < num
      +      cur = cur.right
      +    # 削除対象ノードは cur の左部分木にある
      +    else
      +      cur = cur.left
      +    end
      +  end
      +  # 削除対象ノードがなければそのまま返す
      +  return if cur.nil?
      +
      +  # 子ノード数 = 0 or 1
      +  if cur.left.nil? || cur.right.nil?
      +    # 子ノード数が 0 / 1 のとき、child = null / その子ノード
      +    child = cur.left || cur.right
      +    # ノード cur を削除する
      +    if cur != @root
      +      if pre.left == cur
      +        pre.left = child
      +      else
      +        pre.right = child
      +      end
      +    else
      +      # 削除ノードが根ノードなら、根ノードを再設定
      +      @root = child
      +    end
      +  # 子ノード数 = 2
      +  else
      +    # 中順走査における cur の次ノードを取得
      +    tmp = cur.right
      +    while !tmp.left.nil?
      +      tmp = tmp.left
      +    end
      +    # ノード tmp を再帰的に削除
      +    remove(tmp.val)
      +    # tmp で cur を上書きする
      +    cur.val = tmp.val
      +  end
      +end
       
      -

      4.   中順走査は順序付けされている

      -

      下図に示すように、二分木の中順走査は「左 \(\rightarrow\)\(\rightarrow\) 右」の走査順序に従い、二分探索木は「左子ノード \(<\) 根ノード \(<\) 右子ノード」のサイズ関係を満たします。

      -

      これは、二分探索木で中順走査を実行するときに、常に次に小さいノードが最初に走査されることを意味し、重要な性質につながります:二分探索木の中順走査のシーケンスは昇順です

      -

      中順走査の昇順性質を使用して、二分探索木で順序付けされたデータを取得するには\(O(n)\)の時間のみが必要で、追加のソート操作は不要であり、非常に効率的です。

      -

      二分探索木の中順走査シーケンス

      -

      図 7-22   二分探索木の中順走査シーケンス

      +
      +コードの可視化 +

      +

      +
      +

      4.   中順走査は昇順

      +

      以下の図に示すように、二分木の中順走査は「左 \(\rightarrow\)\(\rightarrow\) 右」という順序に従い、二分探索木は「左子ノード \(<\) 根ノード \(<\) 右子ノード」という大小関係を満たします。

      +

      これは、二分探索木で中順走査を行うと常に次の最小ノードが優先して走査されることを意味し、そこから重要な性質が導かれます。二分探索木の中順走査列は昇順です

      +

      中順走査が昇順になる性質を利用すれば、二分探索木から整列済みデータを取得するのに必要な時間は \(O(n)\) のみで、追加のソート操作は不要です。非常に効率的です。

      +

      二分探索木の中順走査列

      +

      図 7-22   二分探索木の中順走査列

      7.4.2   二分探索木の効率

      -

      データのセットが与えられた場合、配列または二分探索木を使用して格納することを検討します。下の表を観察すると、二分探索木のすべての操作は対数時間計算量を持ち、安定して効率的です。配列は、頻繁な追加と検索や削除の頻度が少ないシナリオでのみ、二分探索木よりも効率的です。

      +

      あるデータ集合が与えられたとき、配列または二分探索木で格納する場合を考えます。次の表を見ると、二分探索木の各操作の時間計算量はいずれも対数オーダーであり、安定して高効率です。高頻度の追加と低頻度の探索・削除という場面でのみ、配列のほうが二分探索木より効率的です。

      表 7-2   配列と探索木の効率比較

      @@ -5018,13 +6008,13 @@ -未ソート配列 +無秩序配列 二分探索木 -要素の検索 +要素の探索 \(O(n)\) \(O(\log n)\) @@ -5041,16 +6031,16 @@
      -

      理想的には、二分探索木は「平衡」しており、任意のノードを\(\log n\)ループ内で見つけることができます。

      -

      しかし、二分探索木で継続的にノードを挿入および削除すると、下図に示すように連結リストに退化する可能性があり、さまざまな操作の時間計算量も\(O(n)\)に悪化します。

      +

      理想的な状況では、二分探索木は「平衡」しており、その場合は \(\log n\) 回のループ内で任意のノードを探索できます。

      +

      しかし、二分探索木でノードの挿入と削除を繰り返すと、二分木が以下の図のような連結リストへ退化する可能性があり、このとき各操作の時間計算量も \(O(n)\) に退化します。

      二分探索木の退化

      図 7-23   二分探索木の退化

      -

      7.4.3   二分探索木の一般的な応用

      +

      7.4.3   二分探索木の代表的な応用

        -
      • システムでの多レベルインデックスとして使用され、効率的な検索、挿入、削除操作を実装します。
      • -
      • 特定の検索アルゴリズムの基盤となるデータ構造として機能します。
      • -
      • データストリームを格納して、その順序付けされた状態を維持するために使用されます。
      • +
      • システム内の多段インデックスとして用いられ、効率的な探索、挿入、削除操作を実現します。
      • +
      • 一部の探索アルゴリズムの基盤データ構造として使われます。
      • +
      • データストリームを格納し、その順序状態を保つために使われます。
      @@ -5098,7 +6088,7 @@ aria-label="フッター"
      @@ -341,7 +341,7 @@ - 序 + はじめに @@ -358,7 +358,7 @@ - 序 + はじめに @@ -618,7 +618,7 @@ - 1.1   アルゴリズムはどこにでもある + 1.1   アルゴリズムは至るところにある @@ -646,7 +646,7 @@ - 1.2   アルゴリズムとは何か + 1.2   アルゴリズムとは @@ -783,7 +783,7 @@ - 2.1   アルゴリズムの効率評価 + 2.1   アルゴリズム効率の評価 @@ -1060,7 +1060,7 @@ - 3.3   数値の符号化 * + 3.3   数値エンコーディング * @@ -1088,7 +1088,7 @@ - 3.4   文字の符号化 * + 3.4   文字エンコーディング * @@ -1591,7 +1591,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1613,7 +1613,7 @@ - 第 6 章   ハッシュ表 + 第 6 章   ハッシュテーブル @@ -1635,7 +1635,7 @@ - 6.1   ハッシュ表 + 6.1   ハッシュテーブル @@ -1889,7 +1889,7 @@ - 7.1.1   二分木の一般的な用語 + 7.1.1   二分木のよく使われる用語 @@ -1912,7 +1912,7 @@ - 1.   二分木の初期化 + 1.   二分木を初期化する @@ -1939,19 +1939,19 @@ - 7.1.3   二分木の一般的な種類 + 7.1.3   一般的な二分木の種類 -

    15.1.2   Свойства жадного алгоритма

    Тогда возникает вопрос: какие задачи подходят для решения жадным алгоритмом? Или, другими словами, в каких случаях жадный алгоритм может гарантировать оптимальный ответ?

    -

    По сравнению с динамическим программированием условия применения жадного алгоритма строже. В основном нас интересуют два свойства задачи.

    +

    По сравнению с динамическим программированием условия применения жадного алгоритма более строгие. В основном нас интересуют два свойства задачи.

    • Свойство жадного выбора: только когда локально оптимальный выбор всегда может привести к глобально оптимальному решению, жадный алгоритм способен гарантировать оптимум.
    • Оптимальная подструктура: оптимальное решение исходной задачи содержит оптимальные решения подзадач.
    • @@ -4737,7 +4737,7 @@

      Pearson, D. A polynomial-time algorithm for the change-making problem[J]. Operations Research Letters, 2005, 33(3): 231-234.

    15.1.3   Этапы решения задач жадным алгоритмом

    -

    В общем виде процесс решения жадной задачи можно разбить на три шага.

    +

    Процесс решения жадной задачи в общем виде можно разбить на три шага.

    1. Анализ задачи: разобраться в свойствах задачи, включая определение состояний, целевой функции и ограничений. Этот этап присутствует и в поиске с возвратом, и в динамическом программировании.
    2. Определение жадной стратегии: определить, какой жадный выбор следует делать на каждом шаге. Эта стратегия должна уменьшать размер задачи на каждом этапе и в итоге привести к решению всей задачи.
    3. @@ -4751,7 +4751,7 @@

      Чтобы гарантировать корректность, нужно дать строгое математическое доказательство жадной стратегии, обычно с использованием доказательства от противного или математической индукции.

      Однако и доказательство корректности может оказаться непростой задачей. Если идей нет, мы обычно начинаем отлаживать код на тестовых примерах, постепенно меняя и проверяя жадную стратегию.

      15.1.4   Типичные задачи для жадного алгоритма

      -

      Жадные алгоритмы часто применяются в задачах оптимизации, которые обладают свойством жадного выбора и оптимальной подструктурой. Ниже приведены некоторые типичные задачи, решаемые жадным подходом.

      +

      Жадные алгоритмы часто применяются в задачах оптимизации, обладающих свойством жадного выбора и оптимальной подструктурой. Ниже приведены некоторые типичные задачи, решаемые жадным подходом.

      • Задача о размене монет: при некоторых системах монет жадный алгоритм всегда дает оптимальный ответ.
      • Задача о расписании интервалов: пусть есть несколько задач, каждая выполняется в некотором временном интервале, и требуется завершить как можно больше задач. Если каждый раз выбирать задачу с самым ранним временем окончания, то жадный алгоритм дает оптимальный ответ.
      • diff --git a/ru/chapter_greedy/index.html b/ru/chapter_greedy/index.html index 49200a279..5993f11b6 100644 --- a/ru/chapter_greedy/index.html +++ b/ru/chapter_greedy/index.html @@ -65,8 +65,8 @@ - - + + @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1591,7 +1591,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1613,7 +1613,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1691,7 +1691,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1786,7 +1786,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1808,7 +1808,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1886,7 +1886,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -1970,7 +1970,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2133,7 +2133,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2224,7 +2224,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2246,7 +2246,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2296,7 +2296,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2352,7 +2352,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2491,7 +2491,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2519,7 +2519,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2547,7 +2547,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2575,7 +2575,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2780,7 +2780,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2808,7 +2808,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3113,7 +3113,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3141,7 +3141,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3418,7 +3418,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3559,7 +3559,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3671,7 +3671,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке diff --git a/ru/chapter_greedy/max_capacity_problem/index.html b/ru/chapter_greedy/max_capacity_problem/index.html index 6e5ce8062..f9b939f6f 100644 --- a/ru/chapter_greedy/max_capacity_problem/index.html +++ b/ru/chapter_greedy/max_capacity_problem/index.html @@ -65,8 +65,8 @@ - - + + @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1591,7 +1591,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1613,7 +1613,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1691,7 +1691,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1786,7 +1786,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1808,7 +1808,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1886,7 +1886,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -1970,7 +1970,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2133,7 +2133,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2224,7 +2224,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2246,7 +2246,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2296,7 +2296,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2352,7 +2352,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2491,7 +2491,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2519,7 +2519,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2547,7 +2547,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2575,7 +2575,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2780,7 +2780,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2808,7 +2808,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3113,7 +3113,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3141,7 +3141,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3418,7 +3418,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3559,7 +3559,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3671,7 +3671,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке @@ -4772,7 +4772,7 @@ cap[i, i+1], cap[i, i+2], \dots, cap[i, j-2], cap[i, j-1]

        Рисунок 15-12   Состояния, пропущенные из-за смещения короткой перегородки

        Нетрудно заметить, что эти пропущенные состояния на самом деле и есть все состояния, в которых длинная перегородка \(j\) сдвигается внутрь. Ранее мы уже доказали, что перемещение длинной перегородки внутрь обязательно уменьшает вместимость. Иными словами, пропущенные состояния не могут быть оптимальным решением, поэтому их пропуск не приводит к потере оптимума.

        -

        Приведенный анализ показывает, что операция перемещения короткой перегородки является «безопасной», а жадная стратегия действительно эффективна.

        +

        Приведенный анализ показывает, что операция перемещения короткой перегородки является «безопасной», а жадная стратегия действительно корректна.

        diff --git a/ru/chapter_greedy/max_product_cutting_problem/index.html b/ru/chapter_greedy/max_product_cutting_problem/index.html index 89e279651..a3a1d8382 100644 --- a/ru/chapter_greedy/max_product_cutting_problem/index.html +++ b/ru/chapter_greedy/max_product_cutting_problem/index.html @@ -65,8 +65,8 @@ - - + + @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1591,7 +1591,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1613,7 +1613,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1691,7 +1691,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1786,7 +1786,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1808,7 +1808,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1886,7 +1886,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -1970,7 +1970,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2133,7 +2133,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2224,7 +2224,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2246,7 +2246,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2296,7 +2296,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2352,7 +2352,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2491,7 +2491,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2519,7 +2519,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2547,7 +2547,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2575,7 +2575,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2780,7 +2780,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2808,7 +2808,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3113,7 +3113,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3141,7 +3141,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3418,7 +3418,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3559,7 +3559,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3671,7 +3671,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке @@ -4421,7 +4421,7 @@ n & \geq 4
      • Если остаток равен \(1\), то поскольку \(2 \times 2 > 1 \times 3\), последний множитель \(3\) следует заменить на \(2\).

    2.   Код реализации

    -

    Как показано на рисунке 15-16, нам не нужен цикл, чтобы выполнять разбиение числа. Можно использовать целочисленное деление вниз, чтобы получить число троек \(a\), и операцию взятия остатка, чтобы получить остаток \(b\). Тогда имеем:

    +

    Как показано на рисунке 15-16, нам не нужен цикл, чтобы выполнять разбиение числа. Можно использовать целочисленное деление, чтобы получить число троек \(a\), и операцию взятия остатка, чтобы получить остаток \(b\). Тогда имеем:

    \[ n = 3 a + b \]
    diff --git a/ru/chapter_greedy/summary/index.html b/ru/chapter_greedy/summary/index.html index 3be744f76..0ef6a78f7 100644 --- a/ru/chapter_greedy/summary/index.html +++ b/ru/chapter_greedy/summary/index.html @@ -65,8 +65,8 @@ - - + + @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1591,7 +1591,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1613,7 +1613,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1691,7 +1691,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1786,7 +1786,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1808,7 +1808,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1886,7 +1886,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -1970,7 +1970,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2133,7 +2133,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2224,7 +2224,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2246,7 +2246,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2296,7 +2296,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2352,7 +2352,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2491,7 +2491,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2519,7 +2519,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2547,7 +2547,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2575,7 +2575,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2780,7 +2780,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2808,7 +2808,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3113,7 +3113,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3141,7 +3141,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3418,7 +3418,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3559,7 +3559,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3671,7 +3671,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке diff --git a/ru/chapter_hashing/hash_algorithm/index.html b/ru/chapter_hashing/hash_algorithm/index.html index 943e1b84d..5931400bc 100644 --- a/ru/chapter_hashing/hash_algorithm/index.html +++ b/ru/chapter_hashing/hash_algorithm/index.html @@ -65,8 +65,8 @@ - - + + @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1593,7 +1593,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1615,7 +1615,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1702,7 +1702,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1720,7 +1720,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1880,7 +1880,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1902,7 +1902,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1980,7 +1980,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -2064,7 +2064,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2227,7 +2227,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2318,7 +2318,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2340,7 +2340,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2390,7 +2390,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2446,7 +2446,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2585,7 +2585,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2613,7 +2613,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2641,7 +2641,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2669,7 +2669,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2874,7 +2874,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2902,7 +2902,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3207,7 +3207,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3235,7 +3235,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3512,7 +3512,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3653,7 +3653,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3765,7 +3765,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке @@ -4410,7 +4410,7 @@

    Из этой формулы видно: при фиксированной емкости хеш-таблицы capacity **выходное значение определяет именно хеш-алгоритм hash() **, а значит, именно он определяет распределение пар ключ-значение в хеш-таблице.

    Это означает, что для уменьшения вероятности хеш-коллизий нам следует сосредоточиться на проектировании хеш-алгоритма hash() .

    6.3.1   Цели хеш-алгоритма

    -

    Чтобы получить структуру данных хеш-таблицы, которая будет одновременно "быстрой и надежной", хеш-алгоритм должен обладать следующими свойствами.

    +

    Чтобы получить структуру данных хеш-таблицы, которая будет одновременно быстрой и надежной, хеш-алгоритм должен обладать следующими свойствами.

    • Детерминированность: для одинакового входа хеш-алгоритм всегда должен выдавать одинаковый результат. Только так хеш-таблица остается надежной.
    • Высокая эффективность: вычисление хеш-значения должно быть достаточно быстрым. Чем меньше вычислительные затраты, тем выше практическая ценность хеш-таблицы.
    • @@ -5030,7 +5030,7 @@

      Следует отметить: если можно гарантировать, что key распределены случайно и равномерно, то выбор простого или составного числа в качестве модуля не так важен - оба варианта способны дать равномерное распределение хеш-значений. Но если в распределении key присутствует периодичность, то взятие по модулю составного числа гораздо легче приводит к кластеризации.

      Итак, на практике мы обычно выбираем простое число в качестве модуля, причем это простое число желательно брать достаточно большим, чтобы по возможности убрать периодические закономерности и повысить устойчивость хеш-алгоритма.

      6.3.3   Распространенные хеш-алгоритмы

      -

      Нетрудно заметить, что описанные выше простые хеш-алгоритмы довольно "хрупкие" и далеки от поставленных целей. Например, сложение и XOR подчиняются коммутативному закону, поэтому аддитивный хеш и XOR-хеш не различают строки, состоящие из одних и тех же символов, но в разном порядке. Это может усиливать хеш-коллизии и даже создавать некоторые проблемы безопасности.

      +

      Нетрудно заметить, что описанные выше простые хеш-алгоритмы довольно хрупкие и далеки от поставленных целей. Например, сложение и XOR подчиняются коммутативному закону, поэтому аддитивный хеш и XOR-хеш не различают строки, состоящие из одних и тех же символов, но в разном порядке. Это может усиливать хеш-коллизии и даже создавать некоторые проблемы безопасности.

      На практике мы обычно используем стандартные хеш-алгоритмы, такие как MD5, SHA-1, SHA-2 и SHA-3. Они могут отображать входные данные произвольной длины в хеш-значения фиксированной длины.

      На протяжении почти ста лет хеш-алгоритмы непрерывно развивались и оптимизировались. Одни исследователи старались повысить их производительность, а другие исследователи и хакеры сосредоточивались на поиске уязвимостей в их безопасности. В таблице 6-2 приведены распространенные хеш-алгоритмы, которые часто встречаются в реальных приложениях.

        diff --git a/ru/chapter_hashing/hash_collision/index.html b/ru/chapter_hashing/hash_collision/index.html index b2dc92b4e..fcf52c000 100644 --- a/ru/chapter_hashing/hash_collision/index.html +++ b/ru/chapter_hashing/hash_collision/index.html @@ -65,8 +65,8 @@ - - + + @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1593,7 +1593,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1615,7 +1615,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1813,7 +1813,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1908,7 +1908,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1930,7 +1930,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -2008,7 +2008,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -2092,7 +2092,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2255,7 +2255,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2346,7 +2346,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2368,7 +2368,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2418,7 +2418,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2474,7 +2474,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2613,7 +2613,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2641,7 +2641,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2669,7 +2669,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2697,7 +2697,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2902,7 +2902,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2930,7 +2930,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3235,7 +3235,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3263,7 +3263,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3540,7 +3540,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3681,7 +3681,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3793,7 +3793,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке @@ -4456,12 +4456,12 @@

        6.2   Хеш-коллизии

        Как уже говорилось в предыдущем разделе, в обычных условиях входное пространство хеш-функции намного больше выходного пространства , поэтому теоретически хеш-коллизии неизбежны. Например, если входное пространство состоит из всех целых чисел, а выходное пространство ограничено размером массива, то неизбежно несколько целых чисел будут отображаться в один и тот же индекс бакета.

        -

        Хеш-коллизии приводят к ошибочным результатам поиска и серьезно влияют на пригодность хеш-таблицы к использованию. Чтобы решить эту проблему, можно при каждом конфликте выполнять расширение хеш-таблицы, пока конфликт не исчезнет. Этот метод прост и груб, но слишком неэффективен, потому что расширение хеш-таблицы требует большого объема переноса данных и вычислений хеш-значений. Чтобы повысить эффективность, можно использовать следующие стратегии.

        +

        Хеш-коллизии могут приводить к ошибочным результатам поиска и серьезно влиять на работоспособность хеш-таблицы. Чтобы решить эту проблему, можно при каждом конфликте выполнять расширение хеш-таблицы, пока конфликт не исчезнет. Этот метод понятен и прост, но слишком неэффективен, потому что расширение хеш-таблицы требует большого объема переноса данных и вычислений хеш-значений. Чтобы повысить эффективность, можно использовать следующие стратегии.

        1. Улучшить структуру данных хеш-таблицы, чтобы она могла корректно работать даже при возникновении хеш-коллизий.
        2. Выполнять расширение только тогда, когда это действительно необходимо, то есть когда хеш-коллизии становятся достаточно серьезными.
        -

        Основные способы улучшения структуры хеш-таблицы включают "метод цепочек" и "открытую адресацию".

        +

        Основные способы улучшения структуры хеш-таблицы включают метод цепочек и открытую адресацию.

        6.2.1   Метод цепочек

        В исходной хеш-таблице каждый бакет может хранить только одну пару ключ-значение. Метод цепочек (separate chaining) превращает отдельный элемент в связный список: пары ключ-значение становятся узлами списка, и все конфликтующие пары ключ-значение хранятся в одном и том же списке. На рисунке 6-5 показан пример хеш-таблицы, реализованной методом цепочек.

        Хеш-таблица с методом цепочек

        @@ -5946,9 +5946,9 @@

        -

        Следует отметить, что когда связный список становится очень длинным, эффективность поиска \(O(n)\) оказывается низкой. В этом случае список можно преобразовать в "AVL-дерево" или "красно-черное дерево" , чтобы оптимизировать временную сложность поиска до \(O(\log n)\) .

        +

        Следует отметить, что когда связный список становится очень длинным, эффективность поиска \(O(n)\) оказывается низкой. В этом случае список можно преобразовать в AVL-дерево или красно-черное дерево , чтобы оптимизировать временную сложность поиска до \(O(\log n)\) .

        6.2.2   Открытая адресация

        -

        Открытая адресация (open addressing) не вводит дополнительных структур данных, а обрабатывает хеш-коллизии с помощью "многократного пробирования"; основные варианты пробирования включают линейное пробирование, квадратичное пробирование и повторное хеширование.

        +

        Открытая адресация (open addressing) не вводит дополнительных структур данных, а обрабатывает хеш-коллизии с помощью многократного пробирования; основные варианты пробирования включают линейное пробирование, квадратичное пробирование и повторное хеширование.

        Ниже на примере линейного пробирования рассмотрим механизм работы хеш-таблицы с открытой адресацией.

        1.   Линейное пробирование

        Линейное пробирование использует линейный поиск с фиксированным шагом. Его методы работы отличаются от обычной хеш-таблицы.

        @@ -5960,7 +5960,7 @@

        Распределение пар ключ-значение в хеш-таблице с открытой адресацией (линейное пробирование)

        Рисунок 6-6   Распределение пар ключ-значение в хеш-таблице с открытой адресацией (линейное пробирование)

        -

        Однако линейное пробирование легко приводит к "кластеризации". Иначе говоря, чем длиннее непрерывная занятая область в массиве, тем выше вероятность новых коллизий в этой области, что еще сильнее способствует росту этой группы и в итоге ухудшает эффективность операций добавления, удаления, поиска и обновления.

        +

        Однако линейное пробирование легко приводит к кластеризации. Иначе говоря, чем длиннее непрерывная занятая область в массиве, тем выше вероятность новых коллизий в этой области, что еще сильнее способствует росту этой группы и в итоге ухудшает эффективность операций добавления, удаления, поиска и обновления.

        Стоит заметить, что мы не можем напрямую удалять элементы из хеш-таблицы с открытой адресацией. Причина в том, что удаление создаст внутри массива пустой бакет None , а при поиске элемента линейное пробирование остановится на этом пустом бакете и вернет результат, из-за чего элементы ниже этого бакета уже не смогут быть найдены, и программа может ошибочно посчитать, что их не существует, как показано на рисунке 6-7.

        Проблема поиска после удаления элемента в открытой адресации

        Рисунок 6-7   Проблема поиска после удаления элемента в открытой адресации

        @@ -5968,7 +5968,7 @@

        Чтобы решить эту проблему, можно использовать механизм ленивого удаления (lazy deletion): он не удаляет элемент из хеш-таблицы напрямую, **а помечает этот бакет специальной константой TOMBSTONE **. В этом механизме и None , и TOMBSTONE означают пустой бакет, и оба могут быть использованы для размещения пары ключ-значение. Но есть важное различие: при линейном пробировании, встретив TOMBSTONE , нужно продолжать обход, потому что ниже него все еще могут существовать пары ключ-значение.

        Однако ленивое удаление может ускорять деградацию производительности хеш-таблицы. Это связано с тем, что каждая операция удаления создает новую метку удаления; по мере роста числа TOMBSTONE время поиска тоже увеличивается, потому что линейное пробирование может быть вынуждено перескакивать через множество TOMBSTONE , прежде чем найдет целевой элемент.

        Поэтому имеет смысл при линейном пробировании запоминать индекс первого встреченного TOMBSTONE и затем менять найденный целевой элемент местами с этим TOMBSTONE . Преимущество такого подхода в том, что при каждом поиске или добавлении элемент будет перемещаться в бакет, расположенный ближе к его идеальной позиции (начальной точке пробирования), а значит, эффективность поиска улучшится.

        -

        Ниже приведена реализация хеш-таблицы с открытой адресацией (линейное пробирование), включающая ленивое удаление. Чтобы пространство хеш-таблицы использовалось более полно, мы рассматриваем ее как "кольцевой массив": когда обход выходит за конец массива, он возвращается к началу и продолжается.

        +

        Ниже приведена реализация хеш-таблицы с открытой адресацией, то есть с линейным пробированием, включающая ленивое удаление. Чтобы пространство хеш-таблицы использовалось более полно, мы рассматриваем ее как кольцевой массив: когда обход выходит за конец массива, он возвращается к началу и продолжается.

        @@ -7698,7 +7698,7 @@
        • Python использует открытую адресацию. В словаре dict для пробирования применяются псевдослучайные числа.
        • Java использует метод цепочек. Начиная с JDK 1.8, когда длина массива внутри HashMap достигает 64, а длина списка достигает 8, этот список преобразуется в красно-черное дерево для повышения производительности поиска.
        • -
        • Go использует метод цепочек. В Go установлено, что каждый бакет может хранить не более 8 пар ключ-значение; при переполнении подключается overflow-bucket, а когда таких bucket становится слишком много, выполняется специальное расширение того же масштаба, чтобы сохранить производительность.
        • +
        • Go использует метод цепочек. В Go установлено, что каждый бакет может хранить не более 8 пар ключ-значение; при переполнении подключается overflow-бакет, а когда таких бакетов становится слишком много, выполняется специальное расширение того же масштаба, чтобы сохранить производительность.
        @@ -7746,7 +7746,7 @@ aria-label="Нижний колонтитул" -

        Нетрудно заметить, что операции чтения, добавления, удаления и обновления в хеш-таблице имеют временную сложность \(O(1)\) , то есть выполняются очень эффективно.

        +

        Нетрудно заметить, что операции поиска, добавления и удаления в хеш-таблице имеют временную сложность \(O(1)\) , то есть выполняются очень эффективно.

        6.1.1   Основные операции с хеш-таблицей

        К базовым операциям хеш-таблицы относятся инициализация, поиск, добавление пар ключ-значение и удаление пар ключ-значение. Пример кода приведен ниже:

        @@ -4694,7 +4694,7 @@ Визуализация выполнения

        https://pythontutor.com/render.html#code=%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%D0%98%D0%BD%D0%B8%D1%86%D0%B8%D0%B0%D0%BB%D0%B8%D0%B7%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%83%0A%20%20%20%20hmap%20%3D%20%7B%7D%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9E%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F%20%D0%B4%D0%BE%D0%B1%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20%23%20%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C%20%D0%B2%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%83%20%D0%BF%D0%B0%D1%80%D1%83%20%D0%BA%D0%BB%D1%8E%D1%87-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%28key%2C%20value%29%0A%20%20%20%20hmap%5B12836%5D%20%3D%20%22%D0%A1%D1%8F%D0%BE%20%D0%A5%D0%B0%22%0A%20%20%20%20hmap%5B15937%5D%20%3D%20%22%D0%A1%D1%8F%D0%BE%20%D0%9B%D0%BE%22%0A%20%20%20%20hmap%5B16750%5D%20%3D%20%22%D0%A1%D1%8F%D0%BE%20%D0%A1%D1%83%D0%B0%D0%BD%D1%8C%22%0A%20%20%20%20hmap%5B13276%5D%20%3D%20%22%D0%A1%D1%8F%D0%BE%20%D0%A4%D0%B0%22%0A%20%20%20%20hmap%5B10583%5D%20%3D%20%22%D0%A3%D1%82%D0%B5%D0%BD%D0%BE%D0%BA%22%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9E%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F%20%D0%BF%D0%BE%D0%B8%D1%81%D0%BA%D0%B0%0A%20%20%20%20%23%20%D0%9F%D0%B5%D1%80%D0%B5%D0%B4%D0%B0%D1%82%D1%8C%20%D0%BA%D0%BB%D1%8E%D1%87%20key%20%D0%B2%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%83%20%D0%B8%20%D0%BF%D0%BE%D0%BB%D1%83%D1%87%D0%B8%D1%82%D1%8C%20%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20value%0A%20%20%20%20name%20%3D%20hmap%5B15937%5D%0A%20%20%20%20%0A%20%20%20%20%23%20%D0%9E%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F%20%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%0A%20%20%20%20%23%20%D0%A3%D0%B4%D0%B0%D0%BB%D0%B8%D1%82%D1%8C%20%D0%B8%D0%B7%20%D1%85%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B%20%D0%BF%D0%B0%D1%80%D1%83%20%D0%BA%D0%BB%D1%8E%D1%87-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%20%28key%2C%20value%29%0A%20%20%20%20hmap.pop%2810583%29&cumulative=false&curInstr=2&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false

        -

        У хеш-таблицы есть три распространенных способа обхода: обход пар ключ-значение, обход ключей и обход значений. Примеры кода приведены ниже:

        +

        Существует три распространенных способа обхода хеш-таблицы: обход пар ключ-значение, обход ключей и обход значений. Примеры кода приведены ниже:

        @@ -4894,7 +4894,7 @@

        6.1.2   Простая реализация хеш-таблицы

        Сначала рассмотрим самый простой случай: реализуем хеш-таблицу только с помощью одного массива. В хеш-таблице каждую пустую ячейку массива мы называем бакетом (bucket), и каждый бакет может хранить одну пару ключ-значение. Следовательно, операция поиска сводится к тому, чтобы найти бакет, соответствующий key , и получить из него value .

        -

        Но как определить бакет, соответствующий заданному key ? Это делается с помощью хеш-функции (hash function). Назначение хеш-функции - отображать большое входное пространство в меньшее выходное пространство. В хеш-таблице входным пространством являются все key , а выходным - все бакеты (индексы массива). Иначе говоря, передав key на вход, мы можем через хеш-функцию получить позицию хранения соответствующей пары ключ-значение в массиве.

        +

        Но как определить бакет, соответствующий заданному key ? Это делается с помощью хеш-функции (hash function). Назначение хеш-функции - отображать большое входное пространство в меньшее выходное пространство. В хеш-таблице входным пространством являются все key , а выходным - все бакеты, то есть индексы массива. Иначе говоря, передав key на вход, мы можем с помощью хеш-функции получить позицию хранения соответствующей пары ключ-значение в массиве.

        Процесс вычисления хеш-функции для одного key включает два шага.

        1. Сначала с помощью некоторого хеш-алгоритма hash() вычисляется хеш-значение.
        2. diff --git a/ru/chapter_hashing/index.html b/ru/chapter_hashing/index.html index a2d6ac8d5..cf95d7e22 100644 --- a/ru/chapter_hashing/index.html +++ b/ru/chapter_hashing/index.html @@ -65,8 +65,8 @@ - - + + @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1593,7 +1593,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1615,7 +1615,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1693,7 +1693,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1788,7 +1788,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1810,7 +1810,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1888,7 +1888,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -1972,7 +1972,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2135,7 +2135,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2226,7 +2226,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2248,7 +2248,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2298,7 +2298,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2354,7 +2354,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2493,7 +2493,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2521,7 +2521,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2549,7 +2549,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2577,7 +2577,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2782,7 +2782,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2810,7 +2810,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3115,7 +3115,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3143,7 +3143,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3420,7 +3420,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3561,7 +3561,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3673,7 +3673,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке @@ -4277,14 +4277,14 @@

          Хеш-таблицы

          Abstract

          -

          В мире компьютеров хеш-таблица похожа на сообразительного библиотекаря.

          -

          Он умеет вычислять шифр хранения и потому быстро находит нужную книгу.

          +

          Хеш-таблица устанавливает соответствие между ключом и значением.

          +

          Благодаря этому она позволяет получать нужное значение по ключу за очень короткое время.

          Содержание главы

          diff --git a/ru/chapter_hashing/summary/index.html b/ru/chapter_hashing/summary/index.html index 815fc16ff..f341ccf3e 100644 --- a/ru/chapter_hashing/summary/index.html +++ b/ru/chapter_hashing/summary/index.html @@ -39,7 +39,7 @@ - 6.4 Краткие итоги - Hello Algo + 6.4 Резюме - Hello Algo @@ -65,8 +65,8 @@ - - + + @@ -152,7 +152,7 @@
          - 6.4   Краткие итоги + 6.4   Резюме
          @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1593,7 +1593,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1615,7 +1615,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1693,7 +1693,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1778,7 +1778,7 @@ - 1.   Основные моменты + 1.   Ключевые выводы @@ -1858,7 +1858,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1880,7 +1880,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1958,7 +1958,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -2042,7 +2042,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2205,7 +2205,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2296,7 +2296,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2318,7 +2318,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2368,7 +2368,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2424,7 +2424,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2563,7 +2563,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2591,7 +2591,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2619,7 +2619,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2647,7 +2647,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2852,7 +2852,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2880,7 +2880,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3185,7 +3185,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3213,7 +3213,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3490,7 +3490,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3631,7 +3631,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3743,7 +3743,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке @@ -4299,7 +4299,7 @@ - 1.   Основные моменты + 1.   Ключевые выводы @@ -4354,8 +4354,8 @@ -

          6.4   Краткие итоги

          -

          1.   Основные моменты

          +

          6.4   Резюме

          +

          1.   Ключевые выводы

          • Передав key , мы можем получить value из хеш-таблицы за \(O(1)\) времени, поэтому она очень эффективна.
          • К типичным операциям хеш-таблицы относятся поиск, добавление пары ключ-значение, удаление пары ключ-значение и обход хеш-таблицы.
          • @@ -4410,7 +4410,7 @@ aria-label="Нижний колонтитул" diff --git a/ru/chapter_heap/build_heap/index.html b/ru/chapter_heap/build_heap/index.html index 994b2a285..a6b4389e3 100644 --- a/ru/chapter_heap/build_heap/index.html +++ b/ru/chapter_heap/build_heap/index.html @@ -65,8 +65,8 @@ - - + + @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1591,7 +1591,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1613,7 +1613,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1691,7 +1691,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1786,7 +1786,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1808,7 +1808,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1886,7 +1886,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -1970,7 +1970,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2216,7 +2216,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2307,7 +2307,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2329,7 +2329,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2379,7 +2379,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2435,7 +2435,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2574,7 +2574,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2602,7 +2602,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2630,7 +2630,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2658,7 +2658,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2863,7 +2863,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2891,7 +2891,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3196,7 +3196,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3224,7 +3224,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3501,7 +3501,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3642,7 +3642,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3754,7 +3754,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке @@ -4377,18 +4377,18 @@

            8.2   Построение кучи

            -

            В некоторых случаях мы хотим построить кучу, используя сразу все элементы списка. Этот процесс называется "построением кучи".

            +

            В некоторых случаях требуется построить кучу, используя сразу все элементы списка. Этот процесс называется построением кучи.

            8.2.1   Реализация через операцию добавления в кучу

            -

            Сначала мы создаем пустую кучу, затем обходим список и для каждого элемента по очереди выполняем "операцию добавления в кучу": сначала помещаем элемент в хвост кучи, а затем выполняем для него упорядочивание "снизу вверх".

            -

            Каждый раз, когда элемент добавляется в кучу, ее длина увеличивается на единицу. Поскольку узлы последовательно добавляются в двоичное дерево сверху вниз, куча строится "сверху вниз".

            +

            Сначала мы создаем пустую кучу, затем обходим список и для каждого элемента по очереди выполняем операцию добавления в кучу: сначала помещаем элемент в хвост кучи, а затем выполняем для него упорядочивание снизу вверх.

            +

            Каждый раз, когда элемент добавляется в кучу, ее длина увеличивается на единицу. Поскольку узлы последовательно добавляются в двоичное дерево сверху вниз, куча строится сверху вниз.

            Пусть число элементов равно \(n\) ; так как каждая операция добавления требует \(O(\log{n})\) времени, временная сложность такого построения кучи составляет \(O(n \log n)\) .

            8.2.2   Реализация через обход и упорядочивание

            На самом деле можно реализовать и более эффективный способ построения кучи, который состоит из двух шагов.

            1. Без изменений добавить все элементы списка в кучу; в этот момент свойства кучи еще не выполняются.
            2. -
            3. Обойти кучу в обратном порядке, то есть в порядке, обратном обходу по уровням, и по очереди выполнить упорядочивание "сверху вниз" для каждого нелистового узла.
            4. +
            5. Обойти кучу в обратном порядке, то есть в порядке, обратном обходу по уровням, и по очереди выполнить упорядочивание сверху вниз для каждого нелистового узла.
            -

            После того как некоторый узел был упорядочен, поддерево с этим узлом в качестве корня становится корректной подкучей. А поскольку обход выполняется в обратном порядке, куча строится "снизу вверх".

            +

            После того как некоторый узел был упорядочен, поддерево с этим узлом в качестве корня становится корректной подкучей. А поскольку обход выполняется в обратном порядке, куча строится снизу вверх.

            Причина выбора обратного обхода в том, что он гарантирует: поддеревья ниже текущего узла уже являются корректными подкучами, а значит, упорядочивание текущего узла действительно будет эффективным.

            Стоит отметить, что листовые узлы не имеют дочерних узлов, поэтому они естественным образом являются корректными подкучами и не требуют упорядочивания. Как показано в коде ниже, последний нелистовой узел является родителем последнего узла, и именно с него мы начинаем обратный обход и упорядочивание:

            @@ -4680,7 +4680,7 @@

            Число узлов на каждом уровне идеального двоичного дерева

            Рисунок 8-5   Число узлов на каждом уровне идеального двоичного дерева

            -

            Как показано на рисунке 8-5, максимальное число итераций упорядочивания "сверху вниз" для некоторого узла равно расстоянию от этого узла до листового узла, а это расстояние как раз и есть "высота узла". Поэтому мы можем просуммировать для каждого уровня выражение "число узлов \(\times\) высота узла" и получить суммарное число итераций упорядочивания для всех узлов.

            +

            Как показано на рисунке 8-5, максимальное число итераций упорядочивания сверху вниз для некоторого узла равно расстоянию от этого узла до листового узла, а это расстояние как раз и есть высота узла. Поэтому мы можем просуммировать для каждого уровня выражение "число узлов \(\times\) высота узла" и получить суммарное число итераций упорядочивания для всех узлов.

            \[ T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \dots + 2^{(h-1)}\times1 \]
            @@ -4703,7 +4703,7 @@ T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline & = O(2^h) \end{aligned} \]
            -

            Далее, число узлов идеального двоичного дерева высоты \(h\) равно \(n = 2^{h+1} - 1\) , поэтому несложно получить сложность \(O(2^h) = O(n)\) . Из этого вывода следует, что построение кучи из входного списка имеет временную сложность \(O(n)\) , что очень эффективно.

            +

            Далее, число узлов идеального двоичного дерева высоты \(h\) равно \(n = 2^{h+1} - 1\) , поэтому несложно получить сложность \(O(2^h) = O(n)\) . Из этого вывода следует, что построение кучи из входного списка имеет временную сложность \(O(n)\) , то есть выполняется очень эффективно.

            @@ -4750,7 +4750,7 @@ aria-label="Нижний колонтитул"
          -

          Детали реализации достаточно сложны; заинтересованные читатели могут изучить соответствующие материалы самостоятельно.

          +

          Детали реализации достаточно сложны; заинтересованные читатели могут обратиться к соответствующим материалам самостоятельно.

          diff --git a/ru/chapter_sorting/quick_sort/index.html b/ru/chapter_sorting/quick_sort/index.html index ab252511e..bcca32d0a 100644 --- a/ru/chapter_sorting/quick_sort/index.html +++ b/ru/chapter_sorting/quick_sort/index.html @@ -65,8 +65,8 @@ - - + + @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1591,7 +1591,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1613,7 +1613,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1691,7 +1691,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1786,7 +1786,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1808,7 +1808,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1886,7 +1886,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -1970,7 +1970,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2133,7 +2133,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2224,7 +2224,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2246,7 +2246,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2296,7 +2296,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2352,7 +2352,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2491,7 +2491,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2519,7 +2519,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2547,7 +2547,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2575,7 +2575,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2782,7 +2782,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2810,7 +2810,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3218,7 +3218,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3246,7 +3246,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3523,7 +3523,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3664,7 +3664,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3776,7 +3776,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке @@ -4463,8 +4463,8 @@

          После завершения разделения исходный массив разбивается на три части: левый подмассив, опорный элемент и правый подмассив; при этом выполняется условие "любой элемент левого подмассива \(\leq\) опорный элемент \(\leq\) любой элемент правого подмассива". Следовательно, далее нам нужно лишь отсортировать эти два подмассива.

          -

          Стратегия divide and conquer в быстрой сортировке

          -

          По сути, разделение с опорным элементом сводит задачу сортировки длинного массива к двум задачам сортировки более коротких массивов.

          +

          Стратегия разделяй и властвуй в быстрой сортировке

          +

          Иными словами, разделение с опорным элементом сводит задачу сортировки длинного массива к двум задачам сортировки более коротких массивов.

          @@ -4985,9 +4985,9 @@
        3. Небольшой константный множитель в сложности: среди трех перечисленных алгоритмов у быстрой сортировки обычно меньше всего сравнений, присваиваний и обменов. Это похоже на причину, по которой "сортировка вставками" часто быстрее "сортировки пузырьком".

      11.5.4   Оптимизация выбора опорного элемента

      -

      На некоторых входных данных временная эффективность быстрой сортировки может ухудшаться. Рассмотрим крайний случай: входной массив полностью отсортирован в обратном порядке. Поскольку в качестве опорного мы выбираем самый левый элемент, после разделения он будет обменян в самый правый конец массива, из-за чего длина левого подмассива станет \(n - 1\) , а длина правого - \(0\) . Если рекурсия будет продолжаться таким образом, то после каждого разделения один из подмассивов будет иметь длину \(0\) , стратегия divide and conquer потеряет смысл, а быстрая сортировка выродится в нечто близкое к "сортировке пузырьком".

      -

      Чтобы по возможности избежать такого сценария, мы можем улучшить стратегию выбора опорного элемента в процедуре разделения. Например, можно выбирать случайный элемент массива как опорный. Однако если не повезет и каждый раз будет выбираться неудачный опорный элемент, производительность все равно останется неудовлетворительной.

      -

      Нужно учитывать, что языки программирования обычно генерируют "псевдослучайные числа". Если специально построить тестовый пример под такую последовательность, эффективность быстрой сортировки все равно может деградировать.

      +

      На некоторых входных данных временная эффективность быстрой сортировки может ухудшаться. Рассмотрим крайний случай: входной массив полностью отсортирован в обратном порядке. Поскольку в качестве опорного мы выбираем самый левый элемент, после разделения он будет обменян в самый правый конец массива, из-за чего длина левого подмассива станет \(n - 1\) , а длина правого - \(0\) . Если рекурсия будет продолжаться таким образом, то после каждого разделения один из подмассивов будет иметь длину \(0\) , стратегия "разделяй и властвуй" потеряет смысл, а быстрая сортировка выродится в нечто близкое к "сортировке пузырьком".

      +

      Чтобы по возможности избежать такого сценария, можно улучшить стратегию выбора опорного элемента в процедуре разделения. Например, можно выбирать случайный элемент массива как опорный. Однако если не повезет и каждый раз будет выбираться неудачный опорный элемент, производительность все равно останется неудовлетворительной.

      +

      Стоит учитывать, что языки программирования обычно генерируют псевдослучайные числа. Если специально построить тестовый пример под такую последовательность, эффективность быстрой сортировки все равно может деградировать.

      Чтобы улучшить ситуацию, можно взять три кандидата (обычно первый, последний и средний элементы массива) и использовать медиану этих трех значений как опорный элемент. Благодаря этому вероятность того, что опорный элемент окажется "не слишком маленьким и не слишком большим", заметно возрастает. Конечно, можно брать и большее число кандидатов, чтобы еще сильнее повысить устойчивость алгоритма. После этого вероятность деградации временной сложности до \(O(n^2)\) существенно уменьшается.

      Пример кода:

      @@ -5748,7 +5748,7 @@ aria-label="Нижний колонтитул" diff --git a/ru/chapter_sorting/radix_sort/index.html b/ru/chapter_sorting/radix_sort/index.html index 8a394e54c..650efc3e2 100644 --- a/ru/chapter_sorting/radix_sort/index.html +++ b/ru/chapter_sorting/radix_sort/index.html @@ -65,8 +65,8 @@ - - + + @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1591,7 +1591,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1613,7 +1613,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1691,7 +1691,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1786,7 +1786,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1808,7 +1808,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1886,7 +1886,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -1970,7 +1970,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2133,7 +2133,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2224,7 +2224,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2246,7 +2246,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2296,7 +2296,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2352,7 +2352,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2491,7 +2491,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2519,7 +2519,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2547,7 +2547,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2575,7 +2575,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2782,7 +2782,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2810,7 +2810,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3185,7 +3185,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3213,7 +3213,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3490,7 +3490,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3631,7 +3631,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3743,7 +3743,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке @@ -4355,8 +4355,8 @@

      11.10   Поразрядная сортировка

      -

      В предыдущем разделе мы познакомились с сортировкой подсчетом: она подходит для случаев, когда объем данных \(n\) велик, а диапазон значений \(m\) сравнительно мал. Предположим теперь, что нужно отсортировать \(n = 10^6\) студенческих идентификаторов, причем каждый идентификатор является \(8\)-значным числом. Тогда диапазон данных \(m = 10^8\) оказывается очень большим; сортировка подсчетом потребует огромного объема памяти, а поразрядная сортировка позволяет этого избежать.

      -

      Поразрядная сортировка (radix sort) по своей основной идее совпадает с сортировкой подсчетом и тоже реализует сортировку через подсчет количества. Поверх этого поразрядная сортировка использует иерархию разрядов числа и последовательно сортирует данные по каждому разряду, получая итоговый упорядоченный результат.

      +

      В предыдущем разделе была рассмотрена сортировка подсчетом: она хорошо подходит для случаев, когда объем данных \(n\) велик, а диапазон значений \(m\) сравнительно мал. Предположим теперь, что нужно отсортировать \(n = 10^6\) номеров студентов, причем каждый номер представляет собой \(8\)-значное число. Тогда диапазон данных \(m = 10^8\) оказывается очень большим; сортировка подсчетом потребует огромного объема памяти, а поразрядная сортировка позволяет этого избежать.

      +

      Поразрядная сортировка (radix sort) по своей основной идее совпадает с сортировкой подсчетом и тоже реализует сортировку через подсчет количества. При этом поразрядная сортировка использует соотношение между разрядами числа и последовательно сортирует данные по каждому разряду, получая итоговый упорядоченный результат.

      11.10.1   Алгоритм

      Рассмотрим пример со студенческими номерами: будем считать, что младший разряд имеет номер \(1\) , а старший - номер \(8\) . Тогда процесс поразрядной сортировки показан на рисунке 11-18.

        @@ -4371,7 +4371,7 @@
        \[ x_k = \lfloor\frac{x}{d^{k-1}}\rfloor \bmod d \]
        -

        где \(\lfloor a \rfloor\) обозначает округление числа \(a\) вниз, а \(\bmod \: d\) означает взятие остатка по модулю \(d\) . Для студенческих идентификаторов выполняется \(d = 10\) и \(k \in [1, 8]\) .

        +

        где \(\lfloor a \rfloor\) обозначает округление числа \(a\) вниз, а \(\bmod \: d\) означает взятие остатка по модулю \(d\) . Для студенческих номеров выполняется \(d = 10\) и \(k \in [1, 8]\) .

        Кроме того, нам нужно слегка изменить код сортировки подсчетом, чтобы он мог сортировать числа по их \(k\)-му разряду:

        diff --git a/ru/chapter_sorting/selection_sort/index.html b/ru/chapter_sorting/selection_sort/index.html index 40860c738..475e6266e 100644 --- a/ru/chapter_sorting/selection_sort/index.html +++ b/ru/chapter_sorting/selection_sort/index.html @@ -65,8 +65,8 @@ - - + + @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1591,7 +1591,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1613,7 +1613,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1691,7 +1691,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1786,7 +1786,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1808,7 +1808,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1886,7 +1886,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -1970,7 +1970,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2133,7 +2133,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2224,7 +2224,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2246,7 +2246,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2296,7 +2296,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2352,7 +2352,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2491,7 +2491,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2519,7 +2519,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2547,7 +2547,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2575,7 +2575,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2841,7 +2841,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2869,7 +2869,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3174,7 +3174,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3202,7 +3202,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3479,7 +3479,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3620,7 +3620,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3732,7 +3732,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке @@ -4337,8 +4337,8 @@

        Пусть длина массива равна \(n\) ; тогда процесс сортировки выбором выглядит так, как показано на рисунке 11-2.

        1. В начальном состоянии все элементы не отсортированы, то есть неотсортированный диапазон индексов равен \([0, n-1]\) .
        2. -
        3. Выбрать минимальный элемент из диапазона \([0, n-1]\) и поменять его местами с элементом в позиции \(0\) . После этого первые 1 элементов массива отсортированы.
        4. -
        5. Выбрать минимальный элемент из диапазона \([1, n-1]\) и поменять его местами с элементом в позиции \(1\) . После этого первые 2 элементов массива отсортированы.
        6. +
        7. Выбрать минимальный элемент из диапазона \([0, n-1]\) и поменять его местами с элементом в позиции \(0\) . После этого первый элемент массива отсортирован.
        8. +
        9. Выбрать минимальный элемент из диапазона \([1, n-1]\) и поменять его местами с элементом в позиции \(1\) . После этого первые два элемента массива отсортированы.
        10. Продолжать по аналогии. После \(n - 1\) раундов выбора и обмена первые \(n - 1\) элементов массива будут отсортированы.
        11. Оставшийся элемент обязательно является максимальным, сортировать его не нужно, поэтому массив считается отсортированным.
        @@ -4692,7 +4692,7 @@ aria-label="Нижний колонтитул"

    Сравнение алгоритмов сортировки

    diff --git a/ru/chapter_stack_and_queue/deque/index.html b/ru/chapter_stack_and_queue/deque/index.html index a29099137..704364b12 100644 --- a/ru/chapter_stack_and_queue/deque/index.html +++ b/ru/chapter_stack_and_queue/deque/index.html @@ -65,8 +65,8 @@ - - + + @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1702,7 +1702,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1724,7 +1724,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1802,7 +1802,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1897,7 +1897,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1919,7 +1919,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1997,7 +1997,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -2081,7 +2081,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2244,7 +2244,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2335,7 +2335,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2357,7 +2357,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2407,7 +2407,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2463,7 +2463,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2602,7 +2602,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2630,7 +2630,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2658,7 +2658,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2686,7 +2686,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2891,7 +2891,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2919,7 +2919,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3224,7 +3224,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3252,7 +3252,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3529,7 +3529,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3670,7 +3670,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3782,7 +3782,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке @@ -4433,7 +4433,7 @@

    5.3   Двусторонняя очередь

    -

    В очереди мы можем удалять элементы только из головы или добавлять их только в хвост. Как показано на рисунке 5-7, двусторонняя очередь (double-ended queue) обеспечивает более высокую гибкость и позволяет выполнять добавление и удаление элементов как с головы, так и с хвоста.

    +

    В обычной очереди мы можем удалять элементы только из головы и добавлять их только в хвост. Как показано на рисунке 5-7, двусторонняя очередь (double-ended queue) обеспечивает большую гибкость и позволяет выполнять добавление и удаление элементов как с головы, так и с хвоста.

    Операции двусторонней очереди

    Рисунок 5-7   Операции двусторонней очереди

    @@ -4832,8 +4832,8 @@

    5.3.2   Реализация двусторонней очереди *

    Реализация двусторонней очереди похожа на реализацию обычной очереди: в качестве базовой структуры данных можно выбрать связный список или массив.

    1.   Реализация на основе двусвязного списка

    -

    Вспомним предыдущий раздел: там мы использовали обычный односвязный список для реализации очереди, потому что он позволяет удобно удалять головной узел (это соответствует операции dequeue) и добавлять новый узел после хвостового узла (это соответствует операции enqueue).

    -

    Для двусторонней очереди и голова, и хвост допускают операции добавления и удаления элементов. Иначе говоря, двусторонняя очередь требует реализации еще одного симметричного направления операций. Поэтому в качестве базовой структуры данных двусторонней очереди мы используем "двусвязный список".

    +

    Вспомним предыдущий раздел: там мы использовали обычный односвязный список для реализации очереди, потому что он позволяет удобно удалять головной узел, что соответствует операции dequeue , и добавлять новый узел после хвостового узла, что соответствует операции enqueue .

    +

    Для двусторонней очереди и голова, и хвост допускают операции добавления и удаления элементов. Иначе говоря, двусторонняя очередь требует реализации еще одного симметричного направления операций. Поэтому в качестве базовой структуры данных двусторонней очереди удобно использовать двусвязный список.

    Как показано на рисунках ниже, мы рассматриваем головной и хвостовой узлы двусвязного списка как голову и хвост двусторонней очереди и одновременно реализуем функции добавления и удаления узлов с обеих сторон.

    @@ -6561,7 +6561,7 @@

    2.   Реализация на основе массива

    -

    Как показано на рисунках ниже, аналогично реализации очереди на массиве мы также можем использовать кольцевой массив для реализации двусторонней очереди.

    +

    Как показано на рисунках ниже, аналогично реализации обычной очереди на массиве мы также можем использовать кольцевой массив для реализации двусторонней очереди.

    @@ -6583,7 +6583,7 @@

    Рисунок 5-9   Операции enqueue и dequeue для двусторонней очереди на массиве

    -

    На основе реализации обычной очереди нужно лишь добавить методы "enqueue в голову" и "dequeue из хвоста":

    +

    На основе реализации обычной очереди нужно лишь добавить методы добавления в голову очереди и удаления из хвоста:

    @@ -7989,7 +7989,7 @@

    5.3.3   Применение двусторонней очереди

    Двусторонняя очередь сочетает в себе логику стека и очереди, поэтому она может покрыть все сценарии применения обеих структур и при этом предоставляет более высокую степень свободы.

    -

    Мы знаем, что функция "undo" в программном обеспечении обычно реализуется с помощью стека: система push-ит каждое изменение в стек, а затем использует pop для отмены. Однако, учитывая ограниченность системных ресурсов, программы обычно ограничивают число шагов отмены (например, разрешают хранить только \(50\) шагов). Когда длина стека превышает \(50\), программе нужно удалить элемент с дна стека (то есть с головы очереди). Но стек не может реализовать такую операцию, и в этом случае его приходится заменять двусторонней очередью. Обрати внимание: основная логика "undo" по-прежнему следует стековому правилу LIFO, просто двусторонняя очередь позволяет более гибко реализовать некоторые дополнительные механизмы.

    +

    Мы знаем, что функция "undo" в программном обеспечении обычно реализуется с помощью стека: система помещает каждое изменение в стек с помощью push , а затем использует pop для отмены. Однако, учитывая ограниченность системных ресурсов, программы обычно ограничивают число шагов отмены, например разрешают хранить только \(50\) шагов. Когда длина стека превышает этот предел, программе нужно удалить элемент с дна стека, то есть с головы очереди. Но стек не может реализовать такую операцию, и в этом случае его приходится заменять двусторонней очередью. Обрати внимание: основная логика "undo" по-прежнему следует стековому правилу LIFO, просто двусторонняя очередь позволяет более гибко реализовать некоторые дополнительные механизмы.

    diff --git a/ru/chapter_stack_and_queue/index.html b/ru/chapter_stack_and_queue/index.html index 683d1cf30..ed8ff6694 100644 --- a/ru/chapter_stack_and_queue/index.html +++ b/ru/chapter_stack_and_queue/index.html @@ -65,8 +65,8 @@ - - + + @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1593,7 +1593,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1615,7 +1615,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1693,7 +1693,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1788,7 +1788,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1810,7 +1810,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1888,7 +1888,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -1972,7 +1972,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2135,7 +2135,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2226,7 +2226,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2248,7 +2248,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2298,7 +2298,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2354,7 +2354,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2493,7 +2493,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2521,7 +2521,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2549,7 +2549,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2577,7 +2577,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2782,7 +2782,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2810,7 +2810,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3115,7 +3115,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3143,7 +3143,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3420,7 +3420,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3561,7 +3561,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3673,7 +3673,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке @@ -4277,8 +4277,8 @@

    Стек и очередь

    Abstract

    -

    Стек похож на стопку кошек, а очередь - на очередь из кошек.

    -

    Эти две структуры соответственно представляют отношения "последним пришел - первым вышел" и "первым пришел - первым вышел".

    +

    Стек и очередь - две базовые линейные структуры данных.

    +

    Они соответственно воплощают принципы "последним пришел - первым вышел" и "первым пришел - первым вышел".

    Содержание главы

      diff --git a/ru/chapter_stack_and_queue/queue/index.html b/ru/chapter_stack_and_queue/queue/index.html index 816e8640a..aabbdf1e4 100644 --- a/ru/chapter_stack_and_queue/queue/index.html +++ b/ru/chapter_stack_and_queue/queue/index.html @@ -65,8 +65,8 @@ - - + + @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1702,7 +1702,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1724,7 +1724,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1802,7 +1802,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1897,7 +1897,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1919,7 +1919,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1997,7 +1997,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -2081,7 +2081,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2244,7 +2244,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2335,7 +2335,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2357,7 +2357,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2407,7 +2407,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2463,7 +2463,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2602,7 +2602,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2630,7 +2630,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2658,7 +2658,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2686,7 +2686,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2891,7 +2891,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2919,7 +2919,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3224,7 +3224,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3252,7 +3252,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3529,7 +3529,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3670,7 +3670,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3782,7 +3782,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке @@ -4434,7 +4434,7 @@

      5.2   Очередь

      Очередь (queue) - это линейная структура данных, подчиняющаяся правилу "первым пришел - первым вышел". Как видно из названия, очередь моделирует обычную ситуацию ожидания: новые люди непрерывно присоединяются к хвосту очереди, а стоящие в начале по одному уходят.

      -

      Как показано на рисунке 5-4, начало очереди называется "головой очереди", а конец - "хвостом очереди"; операцию добавления элемента в хвост называют "enqueue", а операцию удаления элемента из головы - "dequeue".

      +

      Как показано на рисунке 5-4, начало очереди называется головой очереди, а конец - хвостом очереди; операцию добавления элемента в хвост называют enqueue, а операцию удаления элемента из головы - dequeue.

      Правило FIFO для очереди

      Рисунок 5-4   Правило FIFO для очереди

      @@ -4470,7 +4470,7 @@
    -

    Мы можем напрямую использовать готовые классы очереди, предоставляемые языками программирования:

    +

    Обычно достаточно использовать готовые классы очереди, предоставляемые языками программирования:

    @@ -4792,7 +4792,7 @@

    5.2.2   Реализация очереди

    Чтобы реализовать очередь, нам нужна такая структура данных, которая позволяет добавлять элементы с одного конца и удалять их с другого; и связный список, и массив этим требованиям удовлетворяют.

    1.   Реализация на основе связного списка

    -

    Как показано на рисунке 5-5, мы можем рассматривать "головной узел" и "хвостовой узел" связного списка как "голову очереди" и "хвост очереди" соответственно, договорившись, что добавлять узлы можно только в хвост, а удалять - только из головы.

    +

    Как показано на рисунке 5-5, мы можем рассматривать головной узел и хвостовой узел связного списка как голову очереди и хвост очереди соответственно, договорившись, что добавлять узлы можно только в хвост, а удалять - только из головы.

    @@ -5712,14 +5712,14 @@

    2.   Реализация на основе массива

    -

    Удаление первого элемента из массива имеет временную сложность \(O(n)\) , из-за чего операция dequeue оказывается неэффективной. Однако этого можно избежать с помощью следующего приема.

    +

    Удаление первого элемента из массива имеет временную сложность \(O(n)\) , из-за чего операция dequeue оказывается неэффективной. Однако этого можно избежать с помощью следующего приема.

    Мы можем использовать переменную front , указывающую на индекс элемента в голове очереди, и поддерживать переменную size , которая хранит длину очереди. Определим rear = front + size ; эта формула дает позицию rear, указывающую на ячейку сразу после хвоста очереди.

    Исходя из этого, эффективный диапазон элементов массива равен [front, rear - 1], а различные операции реализуются, как показано на рисунке 5-6.

      -
    • Операция enqueue: записать входной элемент по индексу rear и увеличить size на 1.
    • -
    • Операция dequeue: просто увеличить front на 1 и уменьшить size на 1.
    • +
    • Операция enqueue: записать входной элемент по индексу rear и увеличить size на 1.
    • +
    • Операция dequeue: просто увеличить front на 1 и уменьшить size на 1.
    -

    Можно увидеть, что и enqueue, и dequeue требуют всего одной операции, а значит обе имеют временную сложность \(O(1)\) .

    +

    Можно увидеть, что и enqueue , и dequeue требуют всего одной операции, а значит обе имеют временную сложность \(O(1)\) .

    @@ -5735,7 +5735,7 @@

    Рисунок 5-6   Операции enqueue и dequeue в реализации очереди на массиве

    -

    Ты можешь заметить еще одну проблему: при непрерывных операциях enqueue и dequeue значения front и rear оба движутся вправо, и когда они доходят до конца массива, дальше сдвигаться уже нельзя. Чтобы решить эту проблему, можно рассматривать массив как "кольцевой массив", у которого начало и конец соединены.

    +

    Ты можешь заметить еще одну проблему: при непрерывных операциях enqueue и dequeue значения front и rear оба движутся вправо, и когда они доходят до конца массива, дальше сдвигаться уже нельзя. Чтобы решить эту проблему, можно рассматривать массив как кольцевой массив, у которого начало и конец соединены.

    Для кольцевого массива нужно сделать так, чтобы front или rear, перешагнув конец массива, сразу возвращались к его началу и продолжали движение. Такую периодичность удобно реализовать с помощью операции взятия остатка, как показано в коде ниже:

    @@ -6666,7 +6666,7 @@

    Выводы сравнения двух реализаций в целом такие же, как и для стека, поэтому здесь мы не будем повторяться.

    5.2.3   Типичные применения очереди

      -
    • Заказы на Taobao. После оформления заказа покупателем заказ попадает в очередь, а затем система обрабатывает заказы по порядку. Во время крупных распродаж, таких как Double 11, за короткое время возникает огромный поток заказов, и высокая конкурентная нагрузка становится ключевой инженерной проблемой.
    • +
    • Очереди заказов. После оформления заказа покупателем заказ попадает в очередь, а затем система обрабатывает заказы по порядку. Во время крупных распродаж за короткое время возникает огромный поток заказов, и высокая конкурентная нагрузка становится ключевой инженерной проблемой.
    • Различные отложенные задачи. Любой сценарий, где нужно реализовать принцип "кто раньше пришел, тот раньше обслуживается", например очередь заданий принтера или очередь блюд на кухне ресторана, хорошо моделируется очередью, которая эффективно поддерживает нужный порядок обработки.
    diff --git a/ru/chapter_stack_and_queue/stack/index.html b/ru/chapter_stack_and_queue/stack/index.html index f5e5b138e..f66bc6a36 100644 --- a/ru/chapter_stack_and_queue/stack/index.html +++ b/ru/chapter_stack_and_queue/stack/index.html @@ -65,8 +65,8 @@ - - + + @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1713,7 +1713,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1735,7 +1735,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1813,7 +1813,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1908,7 +1908,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1930,7 +1930,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -2008,7 +2008,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -2092,7 +2092,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2255,7 +2255,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2346,7 +2346,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2368,7 +2368,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2418,7 +2418,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2474,7 +2474,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2613,7 +2613,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2641,7 +2641,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2669,7 +2669,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2697,7 +2697,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2902,7 +2902,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2930,7 +2930,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3235,7 +3235,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3263,7 +3263,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3540,7 +3540,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3681,7 +3681,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3793,7 +3793,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке @@ -4456,8 +4456,8 @@

    5.1   Стек

    Стек (stack) - это линейная структура данных, подчиняющаяся логике "последним пришел - первым вышел".

    -

    Стек можно сравнить со стопкой тарелок на столе. Если разрешено перемещать только одну тарелку за раз, то, чтобы достать тарелку снизу, сначала придется по одной убрать все тарелки сверху. Если заменить тарелки различными элементами (например целыми числами, символами, объектами и т.д.), получится структура данных "стек".

    -

    Как показано на рисунке 5-1, верхнюю часть стопки элементов мы называем "вершиной стека", а нижнюю - "основанием стека". Операция добавления элемента на вершину называется "push", а операция удаления верхнего элемента - "pop".

    +

    Стек можно сравнить со стопкой тарелок на столе. Если разрешено перемещать только одну тарелку за раз, то, чтобы достать тарелку снизу, сначала придется по одной убрать все тарелки сверху. Если заменить тарелки различными элементами, например целыми числами, символами, объектами и т.д., получится структура данных "стек".

    +

    Как показано на рисунке 5-1, верхнюю часть стопки элементов мы называем вершиной стека, а нижнюю - основанием стека. Операция добавления элемента на вершину называется push, а операция удаления верхнего элемента - pop.

    Правило LIFO для стека

    Рисунок 5-1   Правило LIFO для стека

    @@ -4493,7 +4493,7 @@
    -

    Обычно мы можем просто использовать встроенный стек, предоставляемый языком программирования. Однако в некоторых языках специальный класс стека может отсутствовать. В таком случае можно использовать "массив" или "связный список" этого языка как стек и в логике программы игнорировать операции, не относящиеся к стеку.

    +

    Обычно достаточно использовать встроенный стек, предоставляемый языком программирования. Однако в некоторых языках специальный класс стека может отсутствовать. В таком случае можно использовать массив или связный список как стек и в логике программы игнорировать операции, не относящиеся к стеку.

    @@ -4805,10 +4805,10 @@

    5.1.2   Реализация стека

    Чтобы глубже понять механизм работы стека, попробуем самостоятельно реализовать класс стека.

    -

    Стек подчиняется принципу LIFO, поэтому мы можем добавлять и удалять элементы только на вершине. Однако и массив, и связный список позволяют добавлять и удалять элементы в произвольном месте. Следовательно, стек можно рассматривать как ограниченный массив или связный список. Иными словами, мы можем "скрыть" часть нерелевантных операций массива или списка, так чтобы внешняя логика соответствовала свойствам стека.

    +

    Стек подчиняется принципу LIFO, поэтому мы можем добавлять и удалять элементы только на вершине. Однако и массив, и связный список позволяют добавлять и удалять элементы в произвольном месте. Следовательно, стек можно рассматривать как ограниченный массив или связный список. Иными словами, мы можем скрыть часть нерелевантных операций массива или списка, так чтобы внешняя логика соответствовала свойствам стека.

    1.   Реализация на основе связного списка

    Если реализовывать стек на основе связного списка, то головной узел списка можно рассматривать как вершину стека, а хвостовой - как основание.

    -

    Как показано на рисунке 5-2, для операции push достаточно вставить элемент в голову связного списка. Такой способ вставки называется "вставкой в голову". Для операции pop достаточно удалить головной узел из списка.

    +

    Как показано на рисунке 5-2, для операции push достаточно вставить элемент в голову связного списка. Такой способ вставки называется вставкой в голову. Для операции pop достаточно удалить головной узел из списка.

    @@ -5580,7 +5580,7 @@

    2.   Реализация на основе массива

    -

    Если реализовывать стек на основе массива, то хвост массива можно рассматривать как вершину стека. Как показано на рисунке 5-3, операции push и pop соответствуют добавлению элемента в конец массива и удалению элемента из конца, обе имеют временную сложность \(O(1)\) .

    +

    Если реализовывать стек на основе массива, то хвост массива можно рассматривать как вершину стека. Как показано на рисунке 5-3, операции push и pop соответствуют добавлению элемента в конец массива и удалению элемента из конца, обе имеют временную сложность \(O(1)\) .

    @@ -6204,21 +6204,21 @@

    Поддерживаемые операции

    Обе реализации поддерживают все операции, определенные для стека. Реализация на массиве дополнительно позволяет выполнять произвольный доступ, но это уже выходит за рамки определения стека и обычно не используется.

    Временная эффективность

    -

    В реализации на массиве и push, и pop выполняются в заранее выделенной непрерывной памяти, которая хорошо использует локальность кэша, поэтому такие операции обычно эффективнее. Однако если при push емкость массива оказывается превышена, включается механизм расширения, и временная сложность конкретно этой операции push становится \(O(n)\) .

    -

    В реализации на связном списке расширение выполняется очень гибко, и проблемы падения эффективности из-за расширения массива здесь нет. Но сама операция push требует инициализации объекта-узла и изменения указателей, поэтому в среднем она немного менее эффективна. Впрочем, если помещаемые в стек элементы уже сами являются объектами-узлами, шаг инициализации можно пропустить и тем самым повысить эффективность.

    +

    В реализации на массиве и push , и pop выполняются в заранее выделенной непрерывной памяти, которая хорошо использует локальность кэша, поэтому такие операции обычно эффективнее. Однако если при push емкость массива оказывается превышена, включается механизм расширения, и временная сложность именно этой операции становится \(O(n)\) .

    +

    В реализации на связном списке расширение выполняется очень гибко, и проблемы падения эффективности из-за расширения массива здесь нет. Но сама операция push требует инициализации объекта-узла и изменения указателей, поэтому в среднем она немного менее эффективна. Впрочем, если помещаемые в стек элементы уже сами являются объектами-узлами, шаг инициализации можно пропустить и тем самым повысить эффективность.

    Итак, когда элементами, помещаемыми и извлекаемыми из стека, являются базовые типы данных, например int или double , можно сделать следующие выводы.

    • Стек на основе массива теряет в эффективности в моменты расширения, но поскольку расширение происходит редко, его средняя эффективность выше.
    • Стек на основе связного списка может обеспечивать более стабильную производительность.

    Пространственная эффективность

    -

    При инициализации списка система выделяет "начальную емкость", которая может превышать реальную потребность. Кроме того, механизм расширения обычно увеличивает емкость по некоторому коэффициенту (например в 2 раза), и расширенная емкость тоже может оказаться больше фактически необходимой. Поэтому реализация стека на основе массива может приводить к некоторым потерям памяти.

    +

    При инициализации массива система выделяет начальную емкость, которая может превышать реальную потребность. Кроме того, механизм расширения обычно увеличивает емкость по некоторому коэффициенту, например в 2 раза, и расширенная емкость тоже может оказаться больше фактически необходимой. Поэтому реализация стека на основе массива может приводить к некоторым потерям памяти.

    Однако, поскольку узлы связного списка должны дополнительно хранить указатели, узлы списка сами по себе занимают больше пространства.

    В итоге нельзя просто сказать, какая из реализаций более экономна по памяти; это нужно анализировать в контексте конкретной задачи.

    5.1.4   Типичные применения стека

      -
    • Кнопки "назад" и "вперед" в браузере, undo и redo в программах. Каждый раз, когда мы открываем новую страницу, браузер помещает предыдущую страницу в стек, чтобы по операции "назад" можно было вернуться к ней. Операция "назад" по сути является pop. Если нужно одновременно поддерживать и "назад", и "вперед", то обычно используются два стека.
    • -
    • Управление памятью программы. Каждый раз при вызове функции система помещает на вершину стека стековый кадр, в котором хранится контекст функции. В рекурсивной функции на этапе углубления рекурсии непрерывно выполняются push-операции, а на этапе возврата - pop-операции.
    • +
    • Кнопки "назад" и "вперед" в браузере, undo и redo в программах. Каждый раз, когда мы открываем новую страницу, браузер помещает предыдущую страницу в стек, чтобы по операции "назад" можно было вернуться к ней. Операция "назад" по сути является pop . Если нужно одновременно поддерживать и "назад", и "вперед", то обычно используются два стека.
    • +
    • Управление памятью программы. Каждый раз при вызове функции система помещает на вершину стека стековый кадр, в котором хранится контекст функции. В рекурсивной функции на этапе углубления рекурсии непрерывно выполняются операции push , а на этапе возврата - операции pop .
    diff --git a/ru/chapter_stack_and_queue/summary/index.html b/ru/chapter_stack_and_queue/summary/index.html index 28aafbe4a..214c22f45 100644 --- a/ru/chapter_stack_and_queue/summary/index.html +++ b/ru/chapter_stack_and_queue/summary/index.html @@ -39,7 +39,7 @@ - 5.4 Краткие итоги - Hello Algo + 5.4 Резюме - Hello Algo @@ -65,8 +65,8 @@ - - + + @@ -152,7 +152,7 @@
    - 5.4   Краткие итоги + 5.4   Резюме
    @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1587,7 +1587,7 @@ - 1.   Основные моменты + 1.   Основные выводы @@ -1663,7 +1663,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1685,7 +1685,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1763,7 +1763,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1858,7 +1858,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1880,7 +1880,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1958,7 +1958,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -2042,7 +2042,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2205,7 +2205,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2296,7 +2296,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2318,7 +2318,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2368,7 +2368,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2424,7 +2424,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2563,7 +2563,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2591,7 +2591,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2619,7 +2619,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2647,7 +2647,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2852,7 +2852,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2880,7 +2880,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3185,7 +3185,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3213,7 +3213,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3490,7 +3490,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3631,7 +3631,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3743,7 +3743,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке @@ -4299,7 +4299,7 @@ - 1.   Основные моменты + 1.   Основные выводы @@ -4354,22 +4354,22 @@ -

    5.4   Краткие итоги

    -

    1.   Основные моменты

    +

    5.4   Резюме

    +

    1.   Основные выводы

    • Стек - это структура данных, следующая правилу "последним пришел - первым вышел", и его можно реализовать с помощью массива или связного списка.
    • -
    • С точки зрения временной эффективности реализация стека на массиве обычно работает быстрее в среднем, но во время расширения емкости временная сложность отдельной операции push может ухудшаться до \(O(n)\) . Напротив, реализация стека на связном списке дает более стабильные характеристики.
    • +
    • С точки зрения временной эффективности реализация стека на массиве обычно работает быстрее в среднем, но во время расширения емкости временная сложность отдельной операции push может ухудшаться до \(O(n)\) . Напротив, реализация стека на связном списке дает более стабильные характеристики.
    • С точки зрения использования памяти реализация стека на массиве может приводить к некоторой потере пространства. Однако следует учитывать, что узлы связного списка занимают больше памяти, чем элементы массива.
    • Очередь - это структура данных, следующая правилу "первым пришел - первым вышел", и ее также можно реализовать с помощью массива или связного списка. Сравнение временной и пространственной эффективности для очереди в целом приводит к тем же выводам, что и для стека.
    • -
    • Двусторонняя очередь - это очередь с более высокой степенью свободы, которая позволяет добавлять и удалять элементы с обеих сторон.
    • +
    • Двусторонняя очередь - это очередь с более высокой степенью свободы, которая позволяет добавлять и удалять элементы с обоих концов.

    2.   Q & A

    Q: Реализованы ли кнопки "вперед" и "назад" в браузере с помощью двусвязного списка?

    -

    По сути, функция переходов "вперед/назад" в браузере отражает логику "стека". Когда пользователь открывает новую страницу, она помещается на вершину стека; когда пользователь нажимает кнопку "назад", эта страница снимается с вершины стека. Двусторонняя очередь позволяет удобно реализовать некоторые дополнительные операции, об этом уже упоминалось в разделе "Двусторонняя очередь".

    +

    По сути, функция переходов "вперед/назад" в браузере отражает логику стека. Когда пользователь открывает новую страницу, она помещается на вершину стека; когда пользователь нажимает кнопку "назад", эта страница снимается с вершины стека. Двусторонняя очередь позволяет удобно реализовать некоторые дополнительные операции, об этом уже упоминалось в разделе "Двусторонняя очередь".

    Q: Нужно ли освобождать память узла после извлечения его из стека?

    Если извлеченный узел еще понадобится, память освобождать не нужно. Если он больше не нужен, то в языках Java и Python есть автоматический сборщик мусора, поэтому ручное освобождение памяти не требуется; в C и C++ память нужно освобождать вручную.

    Q: Двусторонняя очередь выглядит как два соединенных стека. Для чего она нужна?

    -

    Двусторонняя очередь похожа на комбинацию стека и очереди или на два соединенных стека. Она выражает логику "стек + очередь", поэтому может покрыть все применения стека и очереди и при этом остается более гибкой.

    +

    Двусторонняя очередь похожа на комбинацию стека и очереди или на два соединенных стека. Она объединяет логику обеих структур, поэтому может покрыть все их применения и при этом остается более гибкой.

    Q: Как именно реализуются отмена (undo) и повтор (redo)?

    Используются два стека: стек A для отмены и стек B для повтора.

      diff --git a/ru/chapter_tree/array_representation_of_tree/index.html b/ru/chapter_tree/array_representation_of_tree/index.html index e8f24a551..f30c2ed6c 100644 --- a/ru/chapter_tree/array_representation_of_tree/index.html +++ b/ru/chapter_tree/array_representation_of_tree/index.html @@ -65,8 +65,8 @@ - - + + @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1591,7 +1591,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1613,7 +1613,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1691,7 +1691,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1788,7 +1788,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1810,7 +1810,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1897,7 +1897,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -1915,7 +1915,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -2053,7 +2053,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2216,7 +2216,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2307,7 +2307,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2329,7 +2329,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2379,7 +2379,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2435,7 +2435,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2574,7 +2574,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2602,7 +2602,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2630,7 +2630,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2658,7 +2658,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2863,7 +2863,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2891,7 +2891,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3196,7 +3196,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3224,7 +3224,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3501,7 +3501,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3642,7 +3642,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3754,7 +3754,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке @@ -4381,11 +4381,11 @@

      Возникает вопрос: можно ли представить двоичное дерево с помощью массива? Ответ: да.

      7.3.1   Представление идеального двоичного дерева

      Сначала разберем простой случай. Если дана идеальная двоичная структура и все ее узлы хранятся в массиве в порядке обхода по уровням, то каждому узлу будет соответствовать единственный индекс массива.

      -

      Из свойств обхода по уровням можно вывести "формулу соответствия" между индексом родителя и индексами дочерних узлов: если индекс некоторого узла равен \(i\) , то индекс его левого дочернего узла равен \(2i + 1\) , а правого - \(2i + 2\) . На рисунке 7-12 показано соответствие между индексами разных узлов.

      +

      Из свойств обхода по уровням можно вывести формулу соответствия между индексом родителя и индексами дочерних узлов: если индекс некоторого узла равен \(i\) , то индекс его левого дочернего узла равен \(2i + 1\) , а правого - \(2i + 2\) . На рисунке 7-12 показано соответствие между индексами разных узлов.

      Представление идеального двоичного дерева массивом

      Рисунок 7-12   Представление идеального двоичного дерева массивом

      -

      Эта формула соответствия играет ту же роль, что и ссылки на узлы в связной структуре . Имея любой узел в массиве, мы можем по формуле получить доступ к его левому и правому дочерним узлам.

      +

      Эта формула соответствия играет ту же роль, что и ссылки на узлы в связной структуре . Имея любой узел в массиве, мы можем с ее помощью получить доступ к его левому и правому дочерним узлам.

      7.3.2   Представление произвольного двоичного дерева

      Идеальное двоичное дерево - лишь частный случай; в обычной двоичной структуре на промежуточных уровнях часто существует множество None . Поскольку последовательность обхода по уровням не содержит этих None , мы не можем по одной лишь этой последовательности определить их количество и расположение. Это означает, что одному и тому же обходу по уровням может соответствовать сразу несколько различных структур двоичного дерева.

      Как показано на рисунке 7-13, для неполной двоичной структуры описанный выше способ представления массивом уже перестает работать.

      diff --git a/ru/chapter_tree/avl_tree/index.html b/ru/chapter_tree/avl_tree/index.html index 9a1be6c56..cf8907472 100644 --- a/ru/chapter_tree/avl_tree/index.html +++ b/ru/chapter_tree/avl_tree/index.html @@ -65,8 +65,8 @@ - - + + @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1591,7 +1591,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1613,7 +1613,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1691,7 +1691,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1788,7 +1788,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1810,7 +1810,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1888,7 +1888,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -2192,7 +2192,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2355,7 +2355,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2446,7 +2446,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2468,7 +2468,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2518,7 +2518,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2574,7 +2574,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2713,7 +2713,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2741,7 +2741,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2769,7 +2769,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2797,7 +2797,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -3002,7 +3002,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -3030,7 +3030,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3335,7 +3335,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3363,7 +3363,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3640,7 +3640,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3781,7 +3781,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3893,7 +3893,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке @@ -7160,7 +7160,7 @@ aria-label="Нижний колонтитул"

      -

      Для заданного набора данных можно рассмотреть хранение либо в массиве, либо в двоичном дереве поиска. Из таблицы ниже видно, что временная сложность операций двоичного дерева поиска имеет логарифмический порядок, поэтому его производительность стабильна и высока. Только в сценариях с очень частыми вставками и редкими поисками и удалениями массив может быть эффективнее, чем двоичное дерево поиска.

      +

      Для заданного набора данных можно рассмотреть хранение либо в массиве, либо в двоичном дереве поиска. Из таблицы ниже видно, что временная сложность операций двоичного дерева поиска имеет логарифмический порядок и обеспечивает стабильную высокую производительность. Только в сценариях с очень частыми вставками и редкими поисками и удалениями массив может быть эффективнее, чем двоичное дерево поиска.

      Таблица 7-2   Сравнение эффективности массива и дерева поиска

      @@ -6064,7 +6064,7 @@ aria-label="Нижний колонтитул" diff --git a/ru/chapter_tree/binary_tree/index.html b/ru/chapter_tree/binary_tree/index.html index 31c22d854..03fac70fd 100644 --- a/ru/chapter_tree/binary_tree/index.html +++ b/ru/chapter_tree/binary_tree/index.html @@ -65,8 +65,8 @@ - - + + @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1591,7 +1591,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1613,7 +1613,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1691,7 +1691,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1788,7 +1788,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1810,7 +1810,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -2058,7 +2058,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -2142,7 +2142,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2305,7 +2305,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2396,7 +2396,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2418,7 +2418,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2468,7 +2468,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2524,7 +2524,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2663,7 +2663,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2691,7 +2691,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2719,7 +2719,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2747,7 +2747,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2952,7 +2952,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2980,7 +2980,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3285,7 +3285,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3313,7 +3313,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3590,7 +3590,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3731,7 +3731,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3843,7 +3843,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке @@ -4555,7 +4555,7 @@

      7.1   Двоичное дерево

      -

      Двоичное дерево (binary tree) - это нелинейная структура данных, представляющая отношения порождения между "предками" и "потомками" и отражающая логику "разделения надвое". Подобно связному списку, базовой единицей двоичного дерева является узел; каждый узел содержит значение, ссылку на левого дочернего узла и ссылку на правого дочернего узла.

      +

      Двоичное дерево (binary tree) - это нелинейная структура данных, представляющая отношения между "предками" и "потомками" и отражающая логику "разделяй и властвуй". Подобно связному списку, базовой единицей двоичного дерева является узел; каждый узел содержит значение, ссылку на левого дочернего узла и ссылку на правого дочернего узла.

      @@ -4734,7 +4734,7 @@

      Каждый узел имеет две ссылки (указателя), которые соответственно указывают на левого дочернего узла (left-child node) и правого дочернего узла (right-child node); данный узел называется родительским узлом (parent node) для этих двух дочерних узлов. Если задан некоторый узел двоичного дерева, то дерево, образованное его левым дочерним узлом и всеми узлами ниже него, называется левым поддеревом (left subtree) этого узла; аналогично определяется правое поддерево (right subtree).

      -

      В двоичном дереве, кроме листовых узлов, все остальные узлы содержат дочерние узлы и непустые поддеревья. Как показано на рисунке 7-1, если рассматривать "узел 2" как родительский, то его левым и правым дочерними узлами будут "узел 4" и "узел 5"; левое поддерево - это "узел 4 и дерево ниже него", а правое поддерево - это "узел 5 и дерево ниже него".

      +

      Узлы, не имеющие дочерних узлов, называют листьями, а все остальные узлы содержат дочерние узлы и непустые поддеревья. Как показано на рисунке 7-1, если рассматривать "узел 2" как родительский, то его левым и правым дочерними узлами будут "узел 4" и "узел 5"; левое поддерево - это "узел 4 и дерево ниже него", а правое поддерево - это "узел 5 и дерево ниже него".

      Родительский узел, дочерние узлы и поддеревья

      Рисунок 7-1   Родительский узел, дочерние узлы и поддеревья

      @@ -4755,7 +4755,7 @@

      Tip

      -

      Обрати внимание: обычно под "высотой" и "глубиной" понимают "число пройденных ребер", но в некоторых задачах или учебниках их могут определять как "число пройденных узлов". В таком случае и высоту, и глубину нужно увеличить на 1 .

      +

      Обычно под "высотой" и "глубиной" понимают "число пройденных ребер", но в некоторых задачах или учебниках их могут определять как "число пройденных узлов". В таком случае и высоту, и глубину нужно увеличить на 1 .

      7.1.2   Базовые операции двоичного дерева

      1.   Инициализация двоичного дерева

      @@ -5104,20 +5104,20 @@

      Tip

      -

      Обрати внимание: вставка узла может изменить исходную логическую структуру двоичного дерева, а удаление узла обычно означает удаление этого узла вместе со всеми его поддеревьями. Поэтому в двоичном дереве операции вставки и удаления обычно являются частью более крупного набора операций, который и реализует осмысленное действие.

      +

      Стоит помнить, что вставка узла может изменить исходную логическую структуру двоичного дерева, а удаление узла обычно означает удаление этого узла вместе со всеми его поддеревьями. Поэтому в двоичном дереве операции вставки и удаления обычно являются частью более крупного набора операций, который и реализует осмысленное действие.

      7.1.3   Распространенные типы двоичных деревьев

      1.   Идеальное двоичное дерево

      Как показано на рисунке 7-4, идеальное двоичное дерево (perfect binary tree) полностью заполнено на всех уровнях. В идеальном двоичном дереве степень листовых узлов равна \(0\) , а у всех остальных узлов степень равна \(2\) ; если высота дерева равна \(h\) , то общее число узлов равно \(2^{h+1} - 1\) , что образует стандартную экспоненциальную зависимость и отражает часто встречающееся в природе явление клеточного деления.

      Tip

      -

      Обрати внимание: в китайскоязычном сообществе идеальное двоичное дерево часто называют полностью заполненным двоичным деревом.

      +

      В китайскоязычном сообществе идеальное двоичное дерево часто называют полностью заполненным двоичным деревом.

      Идеальное двоичное дерево

      Рисунок 7-4   Идеальное двоичное дерево

      2.   Полное двоичное дерево

      -

      Как показано на рисунке 7-5, полное двоичное дерево (complete binary tree) допускает неполное заполнение только на самом нижнем уровне, причем узлы этого уровня должны непрерывно заполняться слева направо. Обрати внимание: идеальное двоичное дерево тоже является полным двоичным деревом.

      +

      Как показано на рисунке 7-5, полное двоичное дерево (complete binary tree) допускает неполное заполнение только на самом нижнем уровне, причем узлы этого уровня должны непрерывно заполняться слева направо. Стоит отметить, что идеальное двоичное дерево тоже является полным двоичным деревом.

      Полное двоичное дерево

      Рисунок 7-5   Полное двоичное дерево

      @@ -5134,7 +5134,7 @@

      7.1.4   Вырождение двоичного дерева

      На рисунке 7-8 показаны идеальная структура двоичного дерева и вырожденная структура. Когда каждый уровень двоичного дерева полностью заполнен узлами, мы получаем "идеальное двоичное дерево"; когда же все узлы смещаются к одной стороне, двоичное дерево вырождается в "связный список".

        -
      • Идеальное двоичное дерево соответствует лучшему случаю и позволяет полностью раскрыть преимущества двоичного дерева с точки зрения "разделяй и властвуй".
      • +
      • Идеальное двоичное дерево соответствует лучшему случаю и позволяет в полной мере раскрыть преимущества подхода "разделяй и властвуй".
      • Связный список представляет противоположную крайность: все операции становятся линейными, а временная сложность деградирует до \(O(n)\) .

      Лучший и худший случаи структуры двоичного дерева

      diff --git a/ru/chapter_tree/binary_tree_traversal/index.html b/ru/chapter_tree/binary_tree_traversal/index.html index b9adf1bb7..e8f0caba5 100644 --- a/ru/chapter_tree/binary_tree_traversal/index.html +++ b/ru/chapter_tree/binary_tree_traversal/index.html @@ -65,8 +65,8 @@ - - + + @@ -574,7 +574,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -596,7 +596,7 @@ - Глава 1. Знакомство с алгоритмами + Глава 1. Введение в алгоритмы @@ -646,7 +646,7 @@ - 1.2 Что такое структуры данных и алгоритмы + 1.2 Что такое алгоритм @@ -1181,7 +1181,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1203,7 +1203,7 @@ - Глава 4. Массив и связный список + Глава 4. Массивы и списки @@ -1309,7 +1309,7 @@ - 4.4 Память и кеш * + 4.4 Оперативная память и кэш * @@ -1591,7 +1591,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1613,7 +1613,7 @@ - Глава 6. Хеширование + Глава 6. Хеш-таблицы @@ -1691,7 +1691,7 @@ - 6.3 Хеш-алгоритмы + 6.3 Алгоритмы хеширования @@ -1788,7 +1788,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -1810,7 +1810,7 @@ - Глава 7. Дерево + Глава 7. Деревья @@ -2014,7 +2014,7 @@ - 7.3 Представление дерева массивом + 7.3 Представление двоичного дерева массивом @@ -2098,7 +2098,7 @@ - 7.6 Резюме + 7.6 Краткие итоги @@ -2261,7 +2261,7 @@ - 8.3 Задача Top-K + 8.3 Задача Top-k @@ -2352,7 +2352,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2374,7 +2374,7 @@ - Глава 9. Граф + Глава 9. Графы @@ -2424,7 +2424,7 @@ - 9.2 Базовые операции над графами + 9.2 Базовые операции графа @@ -2480,7 +2480,7 @@ - 9.4 Резюме + 9.4 Краткие итоги @@ -2619,7 +2619,7 @@ - 10.2 Точка вставки двоичного поиска + 10.2 Двоичный поиск точки вставки @@ -2647,7 +2647,7 @@ - 10.3 Граничные случаи двоичного поиска + 10.3 Двоичный поиск границ @@ -2675,7 +2675,7 @@ - 10.4 Стратегия оптимизации через хеширование + 10.4 Стратегии оптимизации хеширования @@ -2703,7 +2703,7 @@ - 10.5 Алгоритмы поиска: новый взгляд + 10.5 Переосмысление алгоритмов поиска @@ -2908,7 +2908,7 @@ - 11.3 Пузырьковая сортировка + 11.3 Сортировка пузырьком @@ -2936,7 +2936,7 @@ - 11.4 Сортировка вставкой + 11.4 Сортировка вставками @@ -3241,7 +3241,7 @@ - 12.1 Алгоритмы разделяй и властвуй + 12.1 Стратегия разделяй и властвуй @@ -3269,7 +3269,7 @@ - 12.2 Стратегия поиска разделяй и властвуй + 12.2 Поисковая стратегия разделяй и властвуй @@ -3546,7 +3546,7 @@ - 13.4 Задача о $n$ ферзях + 13.4 Задача о n ферзях @@ -3687,7 +3687,7 @@ - 14.1 Введение в динамическое программирование + 14.1 Первое знакомство с динамическим программированием @@ -3799,7 +3799,7 @@ - 14.5 Задача о неограниченном рюкзаке + 14.5 Задача о полном рюкзаке @@ -4471,7 +4471,7 @@

      К распространенным способам обхода двоичного дерева относятся обход по уровням, прямой обход, симметричный обход и обратный обход.

      7.2.1   Обход по уровням

      Как показано на рисунке 7-9, обход по уровням (level-order traversal) проходит двоичное дерево сверху вниз по уровням и на каждом уровне посещает узлы слева направо.

      -

      По своей сути обход по уровням относится к обходу в ширину (breadth-first traversal), также называемому поиском в ширину (breadth-first search, BFS); он отражает идею "расширяться слой за слоем наружу".

      +

      По своей сути обход по уровням относится к обходу в ширину (breadth-first traversal), также называемому поиском в ширину (breadth-first search, BFS); он отражает идею "расширяться от центра к периферии слой за слоем".

      Обход двоичного дерева по уровням

      Рисунок 7-9   Обход двоичного дерева по уровням

      @@ -4778,8 +4778,8 @@
    1. Пространственная сложность равна \(O(n)\) : в худшем случае, то есть для полной двоичной деревообразной структуры, до достижения самого нижнего уровня в очереди одновременно может находиться до \((n + 1) / 2\) узлов, что требует \(O(n)\) памяти.

7.2.2   Прямой, симметричный и обратный обходы

-

Соответственно, прямой, симметричный и обратный обходы относятся к обходу в глубину (depth-first traversal), также называемому поиском в глубину (depth-first search, DFS); он отражает идею "сначала идти до конца, затем откатываться и продолжать".

-

На рисунке 7-10 показан принцип работы обхода двоичного дерева в глубину. Обход в глубину похож на то, как будто мы обходим всю двоичную структуру по внешнему контуру , и у каждого узла встречаем три позиции, соответствующие прямому, симметричному и обратному обходам.

+

Соответственно, прямой, симметричный и обратный обходы относятся к обходу в глубину (depth-first traversal), также называемому поиском в глубину (depth-first search, DFS); он отражает идею "сначала идти до конца, затем возвращаться и продолжать".

+

На рисунке 7-10 показан принцип работы обхода двоичного дерева в глубину. Обход в глубину можно представить как обход всей двоичной структуры по внешнему контуру , и у каждого узла встречаются три позиции, соответствующие прямому, симметричному и обратному обходам.

Прямой, симметричный и обратный обходы двоичного дерева поиска

Рисунок 7-10   Прямой, симметричный и обратный обходы двоичного дерева поиска

@@ -5328,7 +5328,7 @@ aria-label="Нижний колонтитул"