<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Study Blog</title>
    <link>https://vibeee.tistory.com/</link>
    <description>개인 프로젝트, 공부한 것들을 정리해두는 블로그입니다.</description>
    <language>ko</language>
    <pubDate>Thu, 9 Jul 2026 17:51:33 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>sungw00</managingEditor>
    <image>
      <title>Study Blog</title>
      <url>https://tistory1.daumcdn.net/tistory/4766050/attach/407a345c974b40a8a6a96baec96b3ba0</url>
      <link>https://vibeee.tistory.com</link>
    </image>
    <item>
      <title>세션값을 전역적으로 사용하기 - HandlerMethodArgumentResolver</title>
      <link>https://vibeee.tistory.com/309</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1. 커스텀 어노테이션 생성&lt;/p&gt;
&lt;pre id=&quot;code_1757343630332&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. ArgumentResolver 구현&lt;/p&gt;
&lt;pre id=&quot;code_1757343653079&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterAnnotation(LoginUser.class) != null
               &amp;amp;&amp;amp; parameter.getParameterType().equals(MemberDto.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter,
                                  ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest,
                                  WebDataBinderFactory binderFactory) throws Exception {
        HttpSession session = webRequest.getNativeRequest(HttpServletRequest.class).getSession(false);
        if (session != null) {
            return session.getAttribute(&quot;member&quot;);
        }
        return null;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. WebMvcConfigurer에 등록&lt;/p&gt;
&lt;pre id=&quot;code_1757343671511&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class WebConfig implements WebMvcConfigurer {

    private final LoginUserArgumentResolver loginUserArgumentResolver;

    public WebConfig(LoginUserArgumentResolver loginUserArgumentResolver) {
        this.loginUserArgumentResolver = loginUserArgumentResolver;
    }

    @Override
    public void addArgumentResolvers(List&amp;lt;HandlerMethodArgumentResolver&amp;gt; resolvers) {
        resolvers.add(loginUserArgumentResolver);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 컨트롤러에서 사용&lt;/p&gt;
&lt;pre id=&quot;code_1757343689912&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@GetMapping(&quot;/profile&quot;)
public String profile(@LoginUser MemberDto member) {
    System.out.println(member.getMemNo()); // 세션에서 자동 주입됨
    return &quot;profile&quot;;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <author>sungw00</author>
      <guid isPermaLink="true">https://vibeee.tistory.com/309</guid>
      <comments>https://vibeee.tistory.com/309#entry309comment</comments>
      <pubDate>Tue, 9 Sep 2025 00:03:36 +0900</pubDate>
    </item>
    <item>
      <title>맥북에서 homebrew를 이용한 kafka 설치 방법</title>
      <link>https://vibeee.tistory.com/308</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Homebrew가 없다면 설치하기 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://brew.sh/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://brew.sh/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1757337018383&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Homebrew&quot; data-og-description=&quot;The Missing Package Manager for macOS (or Linux).&quot; data-og-host=&quot;brew.sh&quot; data-og-source-url=&quot;https://brew.sh/&quot; data-og-url=&quot;https://brew.sh/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/nWLh3/hyZGl5l8qv/sHVl635L0Laj1iNDQZ6iY0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/tWlsx/hyZIOSmWFf/ACEXuuZZB7qY8h7mWIEmvk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://brew.sh/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://brew.sh/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/nWLh3/hyZGl5l8qv/sHVl635L0Laj1iNDQZ6iY0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/tWlsx/hyZIOSmWFf/ACEXuuZZB7qY8h7mWIEmvk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Homebrew&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The Missing Package Manager for macOS (or Linux).&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;brew.sh&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이후 아래 명령어로 설치 진행~&lt;/p&gt;
&lt;pre id=&quot;code_1757337057820&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ brew install kafka&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카 실행 / 종료 명령어&lt;/p&gt;
&lt;pre id=&quot;code_1757337088821&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 실행 명령어
$ brew services start kafka

# 실행 성공.
==&amp;gt; Successfully started `kafka` (label: homebrew.mxcl.kafka)

# 종료 명령어
$ brew services stop kafka&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>sungw00</author>
      <guid isPermaLink="true">https://vibeee.tistory.com/308</guid>
      <comments>https://vibeee.tistory.com/308#entry308comment</comments>
      <pubDate>Mon, 8 Sep 2025 22:11:57 +0900</pubDate>
    </item>
    <item>
      <title>기본 자료구조 - 연결 리스트</title>
      <link>https://vibeee.tistory.com/55</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;리스트란?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 구조의 하나로, 데이터를 일직선으로 나열한 형태를 가지고 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 추가나 삭제는 쉽지만, 원하는 데이터에 접근하려면 시간이 많이 걸림. = 삽입과 삭제가 빠름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결 리스트를 단순한 배열로 구현하면 데이터를 삽입, 삭제할 때마다 데이터를 옮겨야 하므로 비효율적인 문제가 발생한다.&amp;nbsp;이 문제를 해결하기 위해 아래에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;포인터&lt;/b&gt;를 이용하여 &lt;b&gt;연결 리스트&lt;/b&gt;를 구현할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y4ZVc/btrIazNElgq/THyifTs4ZUuBumalNE7y30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y4ZVc/btrIazNElgq/THyifTs4ZUuBumalNE7y30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y4ZVc/btrIazNElgq/THyifTs4ZUuBumalNE7y30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy4ZVc%2FbtrIazNElgq%2FTHyifTs4ZUuBumalNE7y30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;730&quot; height=&quot;138&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림이 리스트의 개념도이다. 여기서는 세 개의 문자열 'Blue', 'Yellow', 'Red'가 데이터로 저장되어 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 각 데이터에는 '포인터(Pointer)'가 있으며, 다음 데이터의 메모리 위치를 가리키고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;'Red'는 마지막 데이터이므로 'Red'의 포인터는 아무것도 가리키지 않는다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;연결리스트에서 사용되는 용어&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;노드(node): 각각의 원소(Blue, Yellow, Red)&lt;/li&gt;
&lt;li&gt;데이터(data): 노드가 가지고 있는 값&lt;/li&gt;
&lt;li&gt;포인터(pointer): 뒤쪽 노드를 가리키는 값&lt;/li&gt;
&lt;li&gt;머리 노드(head node): 맨 앞에 있는 노드&lt;/li&gt;
&lt;li&gt;꼬리 노드(tail node): 맨 끝에 있는 노드&lt;/li&gt;
&lt;li&gt;앞쪽 노드(prodecessor node): 각 노드에서 바로 앞에 있는 노드&lt;/li&gt;
&lt;li&gt;뒤쪽 노드(successor node): 각 노드에서 바로 뒤에 있는 노드&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;메모리에 데이터가 저장되는 방식&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;662&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ejsk0G/btrIcGzuw1c/rLh7D5TOlHvpuvfFMFHcpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ejsk0G/btrIcGzuw1c/rLh7D5TOlHvpuvfFMFHcpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ejsk0G/btrIcGzuw1c/rLh7D5TOlHvpuvfFMFHcpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fejsk0G%2FbtrIcGzuw1c%2FrLh7D5TOlHvpuvfFMFHcpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;426&quot; height=&quot;662&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;662&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트는 데이터가 메모리상의 연속된 위치에 저장되지 않아도 되며, 일반적으로 떨어진 영역에 흩어져서 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 말하면 흩어져서 저장돼 있으므로 포인터를 처음부터 순서대로 따라가야만 원하는 데이터에 접근할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;78&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUx9ST/btrIiJ8QRIv/j8BuMqNQFLTPwhRbFHosw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUx9ST/btrIiJ8QRIv/j8BuMqNQFLTPwhRbFHosw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUx9ST/btrIiJ8QRIv/j8BuMqNQFLTPwhRbFHosw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUx9ST%2FbtrIiJ8QRIv%2Fj8BuMqNQFLTPwhRbFHosw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;730&quot; height=&quot;78&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;78&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 위 그림과 같이 'Red'에 접근하자 할 때는 먼저 'Blue'에 접근해야 하고, 그 다음 'Yellow'를 따라가야만 'Red'에 접근할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;데이터 추가&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 추가는 추가할 위치의 앞 뒤 포인터를 변경하는 방식으로 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 'Blue'와 'Yellow' 사이에 'Green'을 추가하고 싶을 때를 생각해보면 다음 그림과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WfH65/btrIdxhMhJD/kt47xwTPNAnl3wWezPLxYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WfH65/btrIdxhMhJD/kt47xwTPNAnl3wWezPLxYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WfH65/btrIdxhMhJD/kt47xwTPNAnl3wWezPLxYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWfH65%2FbtrIdxhMhJD%2Fkt47xwTPNAnl3wWezPLxYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;730&quot; height=&quot;278&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'Blue'의 포인터를 'Green'을 가리키도록 변경하고, 'Green'의 포인터를 'Yellow'를 다음 그림과 같이 가리키도록 하면 데이터 추가가 완료된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/domqQo/btrIfRNpoJk/9zMgPv3KeRQRrAZ5z0Ktk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/domqQo/btrIfRNpoJk/9zMgPv3KeRQRrAZ5z0Ktk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/domqQo/btrIfRNpoJk/9zMgPv3KeRQRrAZ5z0Ktk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdomqQo%2FbtrIfRNpoJk%2F9zMgPv3KeRQRrAZ5z0Ktk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;730&quot; height=&quot;278&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;데이터 삭제&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 삭제도 데이터 추가와 같은 방식으로 진행된다. 포인터의 방향만 바꾸어주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 'Yellow'를 삭제하고 싶다면 'Green'의 포인터를 'Yellow'가 아닌 'Red'를 가리키도록 변경하면 삭제가 완료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때, 'Yellow'는 메모리에는 남지만 어디에서도 접근할 수 없으므로 일부러 삭제할 필요는 없다. 이후에 이 영역이 사용될 때 덮어쓰기가 되어 다시 사용할 수 있게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rI4TG/btrIfsNpL0w/IaTzLQtvKjmS21raZ2AZR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rI4TG/btrIfsNpL0w/IaTzLQtvKjmS21raZ2AZR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rI4TG/btrIfsNpL0w/IaTzLQtvKjmS21raZ2AZR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrI4TG%2FbtrIfsNpL0w%2FIaTzLQtvKjmS21raZ2AZR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;730&quot; height=&quot;278&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;리스트의 시간복잡도&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터에 접근할 때 리스트의 앞에서부터 차례로 진행(선형 탐색)하기 때문에 접근하고 싶은 데이터가 뒤쪽에 있는 경우에는 O(n)의 계산 시간이 걸린다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, 데이터 추가는 두 개의 포인터만 변경하면 되기 때문에 n에 관계없는 상수 시간 O(1)이 된다. 물론, 추가하고 싶은 위치에 이미 접근해 있는 것을 전제로 할때이다. 또한 삭제도 마찬가지로 O(1)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포인터로 연결 리스트 만들기&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4; text-align: start;&quot;&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;# 포인터로 연결 리스트 구현하기

from __future__ import annotations
from typing import Any, Type

class Node:
    &quot;&quot;&quot;연결 리스트용 노드 클래스&quot;&quot;&quot;

    def __init__(self, data: Any = None, next: Node = None):
        &quot;&quot;&quot;초기화&quot;&quot;&quot;
        self.data = data    # 데이터
        self.next = next    # 뒤쪽 포인터&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;노드 클래스 Node&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드 클래스 Node에는 다음과 같은 필드와 __init__( ) 함수가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;필드&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 살펴보았듯이 노드 클래스 Node는 다음과 같이 필드 2개로 구성된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;data: 데이터(데이터에 대한 참조: 임의의 형)&lt;/li&gt;
&lt;li&gt;next: 뒤쪽 포인터(뒤쪽 노드에 대한 참조: Node형)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;__init__( ) 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;__init__( ) 함수는 전달받은 data와 next를 해당 필드에 대입한다. 호출할 때 어떤 인수도 생략할 수 있으며, 생략할 경우에는 None으로 간주한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬의 리스트는 자료구조가 아니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결 리스트는 임의의 위치에 원소를 삽입하거나 삭제할 때 빠르게 수행할 수 있다는 장점이 있다. 하지만 기억 영역(메모리)과 속도 면에서는 배열보다 효율이 뒤떨어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬의 리스트는 이러한 연결 리스트의 자료구조가 아니라 모든 원소를 연속으로 메모리에 배치하는 '배열'로 내부에서 구현하고 있다. 그로므로 속도가 급격히 떨어지지는 않는다. 또 원소를 하나씩 추가, 삽입할 때마다 내부에서 메모리를 확보하거나 해제하지 않는다. 실제 필요한 메모리보다 여유 있게 미리 마련해 놓기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;연결 리스트 클래스&amp;nbsp;&lt;/h4&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4; text-align: start;&quot;&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;class LinkedList:
    &quot;&quot;&quot;연결 리스트 클래스&quot;&quot;&quot;

    def __init__(self) -&amp;gt; None:
        &quot;&quot;&quot;초기화&quot;&quot;&quot;
        self.no = 0         # 노드의 개수
        self.head = None    # 머리 노드
        self.current = None # 주목 노드

    def __len__(self) -&amp;gt; int:
        &quot;&quot;&quot;연결 리스트의 노드 개수를 반환&quot;&quot;&quot;
        return self.no&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;연결 리스트 클래스 LinkedList&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결 리스트 클래스 LinkedList는 다음과 같이 필드 3개로 구성된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;no: 리스트에 등록되어 있는 노드의 개수&lt;/li&gt;
&lt;li&gt;head: 머리 노드에 대한 참조&lt;/li&gt;
&lt;li&gt;current: 현재 주목하고 있는 노드에 대한 참조이며, 주목 포인터라고 부를 수 있음. 리스트에서 노드를 검색하여 그 노드를 주목한 직후에 노드를 삭제하는 등의 용도로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;초기화하는 __init__( ) 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결 리스트 클래스 LinkedList의 __init__( ) 함수는 노드가 하나도 없는 빈 연결 리스트를 생성한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림처럼 머리 노드를 참조하기 위한 Node형 필드 head에 None을 대입한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;head는 머리 노드에 대한 참조일뿐 머리 노드 그 자체가 아님을 주의해야 한다. 노드가 존재하지 않는 빈 연결 리스트는 head가 참조하는 곳이 없으므로(참조해야 하는 노드가 존재하지 않으므로) 그 값을 None으로 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;노드 개수를 반환하는 __len__( ) 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결 리스트의 노드 개수를 반환하는 함수이다. no 값을 그대로 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이 함수를 구현함으로써 연결 리스트를 len( ) 함수의 인수로 전달받을 수 있다. 즉, len( ) 함수로 연결 리스트의 노드 개수를 알아낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;빈 연결 리스트&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림처럼 연결 리스트가 비어 있을(노드가 하나도 존재하지 않을) 때 head값은 None이다. 그러므로 연결 리스트가 비어 있는지는 다음 식으로 검사할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1727938596575&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;head is None	# 연결 리스트가 비어 있는지 확인&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;노드가 1개인 연결 리스트&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림은 노드가 하나만 존재하는 연결 리스트이다. Node형 필드인 head가 참조하는 곳은 머리 노드 A이다. 이 머리 노드 A는 리스트의 꼬리 노드이기도 하므로 뒤쪽 포인터의 값은 None이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;head가 참조하는 뒤쪽 포인터의 값이 None이므로 연결 리스트에 존재하는 노드가 하나뿐인지는 다음 식으로 수행할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1727938702348&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;head.next is None	# 연결 리스트의 노드가 1개인지 확인&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;노드가 2개인 연결 리스트&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림은 노드가 2개 있는 연결 리스트이다. 머리 노드는 노드 A이고, 꼬리 노드는 노드 B이다. 이때 head가 참조하는 곳인 노드 A의 뒤쪽 포인터 next가 노드 B를 참조한다.(곧, head.next가 참조하는 곳은 노드 B이다.) 맨 끝에 위치한 노드 B의 뒤쪽 포인터가 None이므로 연결 리스트의 노드가 2개인지는 다음 식으로 수행할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1727939028847&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;head.next.next is None	# 연결 리스트의 노드가 2개인지 확인&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;뒤쪽 포인터가 아니라 데이터를 나타내는 식을 생각해보았을 때, 노드 A의 데이터에 대한 참조를 나타낸 식은 head.data이고, 노드 B의 데이터에 대한 참조를 나타낸 식은 head.next.data이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 살펴본 3가지 경우의 판단은 no == 0, no == 1, no == 2를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;꼬리 노드의 판단&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node형인 변수 p가 리스트에 있는 노드를 참조한다면, 이때 p가 참조하는 노드가 연결 리스트의 꼬리 노드인지는 다음 식으로 수행할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1727939142659&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;p.next is None	# p가 참조하는 노드가 꼬리 노드인지 확인&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;검색을 수행하는 search( ) 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인수로 주어진 데이터 data와 값이 같은 노드를 검색하는 함수이다. 검색 알고리즘은 선형 검색을 사용한다. 아래 그림과 같이 목적 노드를 만날 때까지 머리 노드부터 순서대로 스캔한다. 아래 그림은 노드 D를 검색하는 모습이고, ①&amp;rarr;②&amp;rarr;③&amp;rarr;④ 순서로 스캔하면 검색에 성공한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1167&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPdQOV/btsJUk7WHYZ/uIAk8kyhZBR30kHS4Dqzbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPdQOV/btsJUk7WHYZ/uIAk8kyhZBR30kHS4Dqzbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPdQOV/btsJUk7WHYZ/uIAk8kyhZBR30kHS4Dqzbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPdQOV%2FbtsJUk7WHYZ%2FuIAk8kyhZBR30kHS4Dqzbk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;536&quot; height=&quot;298&quot; data-origin-width=&quot;1167&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드를 스캔할 때 다음 조건 가운데 하나만 성립해도 검색이 종료된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;종료 조건 1: 검색 조건을 만족하는 노드를 발견하지 못하고 꼬리 노드까지 왔을 경우&lt;/li&gt;
&lt;li&gt;종료 조건 2: 검색 조건을 만족하는 노드를 발견한 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 검색하는 과정을 나타낸다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4; text-align: start;&quot;&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;def search(self, data: Any) -&amp;gt; int:
    &quot;&quot;&quot;data와 값이 같은 노드를 검색&quot;&quot;&quot;
    cnt = 0
    ptr = self.head
    while ptr is not None:
        if ptr.data == data:
            self.current = ptr
            return cnt
        cnt += 1
        ptr = ptr.next
    return -1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;31~32행(cnt와 ptr에 값을 대입하는 부분): 스캔 중인 노드를 참조하기 위한 변수 ptr을 head로 초기화한다. 아래 그림의 a처럼 ptr이 참조하는 곳은 head가 참조하는 머리 노드 A가 된다. 그리고 맨 앞에서 몇 번째 원소를 스캔하고 있는지를 나타낸 카운터용 변수 cnt를 0으로 초기화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;33~38행(while문 내부): 앞에서 정리한 종료 조건 1의 판단을 수행한다. ptr값이 None이 아니면, 루프 본문의 34~36행과 37~38행을 실행한다. ptr의 값이 None이면 스캔할 노드가 존재하지 않으므로 while문을 종료하고 39행(return -1 부분)으로 간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;34~36행(if문 부분): 앞에서 정리한 종료 조건 2의 판단을 수행하며, 검색할 data와 스캔 중인 노드의 데이터 ptr.data와 값이 같은지 판단한다. 값이 같으면 검색 성공이다. 주목 포인터 current에 ptr을 대입하고 찾은 노드의 위치를 나타내는 카운터 cnt를 반환한다. 참고로 cnt는 0부터 시작하는 값이다.(찾은 노드가 맨 앞이면 0이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;37~38행(while문 내부의 cnt와 ptr 부분): ptr에 ptr.next를 대입하고 다음 노드로 스캔을 진행한다. 참고로 ptr이 노드 A를 참조하는 아래 그림의 a 상태에서 ptr = ptr.next의 대입을 실행하면 b가 된다. 뒤쪽 노드 B에 대한 참조인 ptr.next가 ptr에 대입된 결과 ptr이 참조하는 곳이 노드 A에서 노드 B로 업데이트 되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;39행(return -1 부분): 프로그램의 흐름이 여기에 도달했다면 검색에 실패한 것이다. 검색 실패임을 나타내는 -1을 반환한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1451&quot; data-origin-height=&quot;1322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnmHzJ/btsJZqGVS9Q/GmnkOYIKU27nRjZXFbVoCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnmHzJ/btsJZqGVS9Q/GmnkOYIKU27nRjZXFbVoCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnmHzJ/btsJZqGVS9Q/GmnkOYIKU27nRjZXFbVoCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnmHzJ%2FbtsJZqGVS9Q%2FGmnkOYIKU27nRjZXFbVoCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;667&quot; height=&quot;608&quot; data-origin-width=&quot;1451&quot; data-origin-height=&quot;1322&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 포함되어 있는지 판단하는 __contains__( ) 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트에 data와 값이 같은 노드가 포함되어 있는지를 판단하는 함수이다. 포함되어 있으면 True를 반환하고, 그렇지 않으면 False를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이 함수를 구현함으로써 연결 리스트에 in 연산자를 적용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머리에 노드를 삽입하는 add_first( ) 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트의 맨 앞에 노드를 삽입하는 함수이다. 아래 그림과 함께 add_first( ) 함수를 이해해보자.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4; text-align: start;&quot;&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;def add_first(self, data: Any) -&amp;gt; None:
    &quot;&quot;&quot;맨 앞에 노드를 삽입&quot;&quot;&quot;
    ptr = self.head # 삽입하기 전의 머리 노드
    self.head = self.current = Node(data, ptr)
    self.no += 1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;48행(ptr 부분): 삽입하기 전의 머리 노드 A를 참조하는 포인터를 ptr에 저장해 둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;49행(self.head 부분): 삽입할 노드 G를 Node(data, ptr)로 생성한다. 노드 G의 데이터는 data가 되고, 뒤쪽 포인터가 참조하는 곳은 ptr(삽입하기 전의 머리 노드 A)이 된다. 이때 수행하는 대입으로 head는 삽입한 노드를 참조하도록 업데이트 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;주목 포인터 current도 삽입한 노드를 참조하도록 업데이트 된다.(꼬리에 노드를 삽입하는 add_last( ) 함수에서도 마찬가지이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림에서는 노드를 구체적으로 삽입하는 과정을 나타낸다. 그림 a에서 리스트 맨 앞의 머리 노드에 노드 G를 삽입하면 그림 b 상태가 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1441&quot; data-origin-height=&quot;794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgVPw7/btsJZnDrb4a/i6SdcXNftktxcddCFbrAOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgVPw7/btsJZnDrb4a/i6SdcXNftktxcddCFbrAOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgVPw7/btsJZnDrb4a/i6SdcXNftktxcddCFbrAOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgVPw7%2FbtsJZnDrb4a%2Fi6SdcXNftktxcddCFbrAOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;675&quot; height=&quot;372&quot; data-origin-width=&quot;1441&quot; data-origin-height=&quot;794&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;꼬리에 노드를 삽입하는 add_last( ) 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트의 맨 끝에 노드를 삽입하는 함수이다. 리스트가 비어 있는지(head is None이 성립하는지) 확인하고 그에 따라 다르게 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림과 함께 add_last( ) 함수를 이해해보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리스트가 비어 있을 때: &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;맨 앞에 노드를 삽입하는 것과 같은 처리를 수행하므로 add_first( ) 함수를 호출한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;리스트가 비어 있지 않을 때: 리스트의 맨 끝에 노드 G를 삽입한다.(아래 그림 b에서 구체적인 삽입 과정을 보여준다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4; text-align: start;&quot;&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;def add_last(self, data: Any):
    &quot;&quot;&quot;맨 끝에 노드를 삽입&quot;&quot;&quot;
    if self.head is None:       # 리스트가 비어 있으면
        self.add_first(data)    # 맨 앞에 노드를 삽입
    else:
        ptr = self.head
        while ptr.next is not None:
            ptr = ptr.next
        ptr.next = self.current = Node(data, None)
        self.no += 1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;59~60행(while문 내부): 꼬리 노드를 찾는 과정을 수행한다. ptr이 참조하는 곳을 그 뒤쪽 포인터로 업데이트 하는 과정을 반복함으로써 노드를 맨 앞부터 순서대로 스캔한다. while문의 반복이 종료되는 것은 ptr.next가 참조하는 곳이 None으로 되었을 때이다. 이때 ptr이 참조하는 곳은 꼬리 노드 F로 되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;while문을 종료할 때 ptr은 꼬리 노드를 참조한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;61행(ptr.next로 시작하는 부분): 삽입하는 노드 G를 Node(data, None)으로 생성한다. 뒤쪽 포인터를 None으로 하는 것은 맨 끝에 위치한 노드 G가 어떤 노드도 참조하지 않도록 하기 위한 것이다. 노드 F의 뒤쪽 포인터 ptr.next가 참조하는 곳이 새로 삽입한 노드 G가 되도록 업데이트 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1643&quot; data-origin-height=&quot;825&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DoDvW/btsJXXF2cSc/KzRqlKIJx3iSKP8zhXXaxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DoDvW/btsJXXF2cSc/KzRqlKIJx3iSKP8zhXXaxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DoDvW/btsJXXF2cSc/KzRqlKIJx3iSKP8zhXXaxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDoDvW%2FbtsJXXF2cSc%2FKzRqlKIJx3iSKP8zhXXaxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;790&quot; height=&quot;397&quot; data-origin-width=&quot;1643&quot; data-origin-height=&quot;825&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;머리 노드를 삭제하는 remove_first( ) 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머리 노드를 삭제하는 함수이다. 삭제 처리를 수행하는 것은 리스트가 비어 있지 않을(head is not None이 성립할)때이다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4; text-align: start;&quot;&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;def remove_first(self) -&amp;gt; None:
    &quot;&quot;&quot;머리 노드를 삭제&quot;&quot;&quot;
    if self.head is not None:   # 리스트가 비어 있지 않다면
        self.head = self.current = self.head.next
    self.no -= 1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림은 구체적인 삭제 과정이다. a에서 머리 노드 A를 삭제하면 b 상태가 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1441&quot; data-origin-height=&quot;747&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BwiN1/btsJYTQtjQV/eKgQkH2w0tajGh5Gngsobk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BwiN1/btsJYTQtjQV/eKgQkH2w0tajGh5Gngsobk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BwiN1/btsJYTQtjQV/eKgQkH2w0tajGh5Gngsobk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBwiN1%2FbtsJYTQtjQV%2FeKgQkH2w0tajGh5Gngsobk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;692&quot; height=&quot;359&quot; data-origin-width=&quot;1441&quot; data-origin-height=&quot;747&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맨 앞에서 2번째 노드 B에 대한 참조인 head.next를 머리 노드에 대한 참조인 head에 대입함으로써 head가 참조하는 곳을 노드 B로 업데이트 한다. 이때 주목 포인터 current가 참조하는 곳도 노드 B로 업데이트 한다. 그 결과 삭제하기 전의 머리 노드 A는 어디에서도 참조되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;리스트에 노드가 하나밖에 없는 경우의 삭제 처리를 생각해보자. 삭제하기 전의 머리 노드는 꼬리 노드이기도 하므로 뒤쪽 포인터 head.next의 값은 None이다. 이 None을 head에 대입하면 리스트는 빈 상태가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;꼬리 노드를 삭제하는 remove_last( ) 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼬리 노드를 삭제하는 함수이다. 삭제 처리를 수행하는 것은 리스트가 비어 있지 않을 때이다. 리스트에 존재하는 노드가 하나뿐인지 확인하고 그에 따라 다음과 같이 다르게 처리한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리스트에 노드가 하나만 존재할 때: 머리 노드를 삭제하는 것이므로 remove_first( ) 함수를 호출한다.&lt;/li&gt;
&lt;li&gt;리스트에 노드가 2개 이상 존재할 때: 리스트의 맨 끝에서 노드 F를 삭제한다. 아래 그림의 b에서 구체적인 삭제 과정을 보여준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4; text-align: start;&quot;&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;def remove_last(self):
    &quot;&quot;&quot;꼬리 노드를 삭제&quot;&quot;&quot;
    if self.head is not None:
        if self.head.next is None:  # 노드가 1개 뿐이라면
            self.remove_first()     # 머리 노드를 삭제
        else:
            ptr = self.head         # 스캔 중인 노드
            pre = self.head         # 스캔 중인 노드의 앞쪽 노드
            
            while ptr.next is not None:
                pre = ptr
                ptr = ptr.next
            pre.next = None         # pre는 삭제 뒤 꼬리 노드
            self.current = pre
            self.no -= 1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;78~83행(else~while문 내부): 꼬리 노드와 맨 끝에서 2번째 노드를 찾는다. 따라서 스캔 방법은 앞에서 살펴본 add_last( ) 함수와 거의 같다. 다만 스캔 중인 노드의 앞쪽 노드를 참조하는 변수 pre를 추가한 점이 다르다. 아래 그림의 경우 while문을 종료할 때 pre가 참조하는 곳은 노드 E이고, ptr이 참조하는 곳은 노드 F이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;즉, while 문을 종료할 때 ptr은 꼬리 노드를 참조하고, pre는 맨 끝에서 2번째 노드를 참조한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;84~86행(pre.next 이후 부분): 맨 끝에서 2번째 노드 E의 뒤쪽 포인터에 None을 대입한다. 그 결과 노드 F는 어디에서도 참조되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;주목 포인터 current가 참조하는 곳은 삭제한 뒤의 꼬리 노드 pre로 업데이트한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1710&quot; data-origin-height=&quot;771&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cahKXh/btsJ0awHHC5/muG0KB8B2jbAwwKLbIHTJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cahKXh/btsJ0awHHC5/muG0KB8B2jbAwwKLbIHTJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cahKXh/btsJ0awHHC5/muG0KB8B2jbAwwKLbIHTJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcahKXh%2FbtsJ0awHHC5%2FmuG0KB8B2jbAwwKLbIHTJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;765&quot; height=&quot;345&quot; data-origin-width=&quot;1710&quot; data-origin-height=&quot;771&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;임의의 노드를 삭제하는 remove( ) 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임의의 노드를 삭제하는 함수이다. 삭제 처리를 수행하는 것은 리스트가 비어 있지 않고 인수로 주어진 노드 p(p가 참조하는 노드)가 존재할 때이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;p가 머리 노드일 때: 머리 노드를 삭제하는 것이므로 remove_first( ) 함수를 호출한다.&lt;/li&gt;
&lt;li&gt;p가 머리 노드가 아닐 때: 리스트에서 p가 참조하는 노드 D를 삭제한다. 아래 그림에서 구체적인 삭제 과정을 보여준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4; text-align: start;&quot;&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;def remove(self, p: Node) -&amp;gt; None:
    &quot;&quot;&quot;노드 p를 삭제&quot;&quot;&quot;
    if self.head is not None:
        if p is self.head:          # p가 머리 노드이면
            self.remove_first()     # 머리 노드를 삭제
        else:
            ptr = self.head

            while ptr.next is not p:
                ptr = ptr.next
                if ptr is None:
                    return          # ptr은 리스트에 존재하지 않음
            ptr.next = p.next
            self.current = ptr
            self.no -= 1

def remove_current_node(self) -&amp;gt; None:
    &quot;&quot;&quot;주목 노드를 삭제&quot;&quot;&quot;
    self.remove(self.current)

def clear(self) -&amp;gt; None:
    &quot;&quot;&quot;전체 노드를 삭제&quot;&quot;&quot;
    while self.head is not None:    # 전체가 비어 있을 때까지
        self.remove_first()         # 머리 노드를 삭제
    self.current = None
    self.no = 0

def next(self) -&amp;gt; bool:
    &quot;&quot;&quot;주목 노드를 한 칸 뒤로 이동&quot;&quot;&quot;
    if self.current is None or self.current.next is None:
        return False                # 이동할 수 없음
    self.current = self.current.next
    return True&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;95~100행(remove 함수 내 else 문의 while문까지): 삭제할 노드 p의 앞쪽 노드를 찾는 과정을 수행한다. while문은 머리 노드에서 시작하여 스캔 중인 노드 ptr의 뒤쪽 포인터인 ptr.next가 p와 같아질 때까지 반복한다. 다만 None을 만나는 경우에는 p가 참조하는 노드가 존재하지 않는다는 것이다. 삭제 처리를 수행하지 않고 return 문으로 함수를 종료한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ptr.next가 p와 같아지면 while 문은 종료한다. 이때 ptr이 참조하는 곳은 삭제할 노드 D의 앞쪽 노드인 C가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;101~103행(remove 함수의 ptr.next 부분): 노드 D의 뒤쪽 포인터 p.next를 노드 C의 뒤쪽 포인터 ptr.next에 대입함으로써 노드 C의 뒤쪽 포인터가 참조하는 곳을 노드 E로 업데이트 한다. 그 결과 노드 D는 어디에서도 참조되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;주목 포인터 current가 참조하는 곳은 삭제한 노드의 앞쪽 노드가 되도록 업데이트 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1441&quot; data-origin-height=&quot;719&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WCpQf/btsJZkz3wEy/yUvcZQL3ViEXyaJPIGgKK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WCpQf/btsJZkz3wEy/yUvcZQL3ViEXyaJPIGgKK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WCpQf/btsJZkz3wEy/yUvcZQL3ViEXyaJPIGgKK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWCpQf%2FbtsJZkz3wEy%2FyUvcZQL3ViEXyaJPIGgKK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;722&quot; height=&quot;360&quot; data-origin-width=&quot;1441&quot; data-origin-height=&quot;719&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주목 노드를 삭제하는 remove_current_node( ) 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 주목하고 있는 노드를 삭제하는 함수이다. 주목 포인터 current를 remove( ) 함수에 전달하여 처리를 맡긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;주목 포인터 current가 참조하는 곳은 삭제한 노드의 앞쪽 노드로 업데이트 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;모든 노드를 삭제하는 clear( ) 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 노드를 삭제하는 함수이다. 연결 리스트가 비어 있을 때(head가 None이 될 때)까지 머리 노드의 삭제를 반복하여 모든 노드를 삭제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;리스트가 비어 있으므로 주목 포인터 current의 값도 None으로 업데이트 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주목 노드를 한 칸 뒤로 이동시키는 next( ) 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주목 노드를 한 칸 뒤로 이동시키는 함수이다. 다만 주목 노드를 한 칸 뒤로 이동시키려면 리스트가 비어 있지 않고 주목 노드에 뒤쪽 노드가 존재해야 한다. 구체적으로는 주목 포인터 current를 current.next로 업데이트 한다. 주목 노드를 이동시키면 True를 반환하고, 그렇지 않으면 False를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주목 노드를 출력하는 print_current_node( ) 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주목 노드를 출력하는 함수이다. 구체적으로 주목 포인터 current가 참조하는 곳의 노드 데이터인 current.data를 출력한다. 다만 주목 노드가 존재하지 않는 경우(current가 None인 경우)에는 '주목 노드가 존재하지 않습니다.'를 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4; text-align: start;&quot;&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;def print_current_node(self) -&amp;gt; None:
    &quot;&quot;&quot;주목 노드를 출력&quot;&quot;&quot;
    if self.current is None:
        print('주목 노드가 존재하지 않습니다.')
    else:
        print(self.current.data)
        
def print(self) -&amp;gt; None:
    &quot;&quot;&quot;모든 노드를 출력&quot;&quot;&quot;
    ptr = self.head
    
    while ptr is not None:
        print(ptr.data)
        ptr = ptr.next&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;모든 노드를 출력하는 print( ) 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트 순서대로 모든 노드의 데이터를 출력하는 함수이다. ptr을 사용하여 머리 노드에서 꼬리 노드까지 스캔하면서 각 노드의 데이터 ptr.data를 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;print_current_node( ) 함수와 print( ) 함수는 주목 포인터 current 값을 업데이트 하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 표는 각 함수를 실행한 뒤의 current 값을 정리한 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1113&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N1XEA/btsJY36Au2a/rrU6Z6nUJk96Pib6BKR9kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N1XEA/btsJY36Au2a/rrU6Z6nUJk96Pib6BKR9kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N1XEA/btsJY36Au2a/rrU6Z6nUJk96Pib6BKR9kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN1XEA%2FbtsJY36Au2a%2FrrU6Z6nUJk96Pib6BKR9kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1113&quot; height=&quot;515&quot; data-origin-width=&quot;1113&quot; data-origin-height=&quot;515&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4; text-align: start;&quot;&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;def __iter__(self) -&amp;gt; LinkedListIterator:
        &quot;&quot;&quot;이터레이터를 반환&quot;&quot;&quot;
        return LinkedListIterator(self.head)

class LinkedListIterator:
    &quot;&quot;&quot;클래스 LinkedList의 이터레이터용 클래스&quot;&quot;&quot;

    def __init__(self, head: Node):
        self.current = head

    def __iter__(self) -&amp;gt; LinkedListIterator:
        return self

    def __next__(self) -&amp;gt; Any:
        if self.current is None:
            raise StopIteration()
        else:
            data = self.current.data
            self.current = self.current.next
            return data&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이터러블 객체와 이터레이터의 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;str형 문자열, list형 리스트, tuple형 튜플 등은 이터러블(반복 가능)하다는 공통점이 있다. 이터러블 객체는 원소를 1개씩 꺼내는 구조의 객체이다. 이터러블 객체를 내장 함수인 iter( ) 함수에 인수로 전달하면 그 객체에 대한 이터레이터(반복자)를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이터레이터는 데이터가 줄지어 늘어선 것을 표현하는 객체이다. 이터레이터의 __next__( ) 함수를 호출하거나, 내장 함수인 next( ) 함수에 반복자를 전달하면 줄지어 늘어선 원소를 순차적으로 꺼낸다. 꺼낼 원소가 없으면 StopIteration 예외 처리를 내보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;next( ) 함수의 첫 번째 호출에서는 맨 앞 원소를 꺼내고, 2번째 호출에서는 2번째 원소를 꺼낸다. 이런 식으로 호출할 때마다 다음 원소를 꺼낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 LinkedList는 이터러블이 되도록 이터레이터를 구현한다. 이터레이터를 나타내는 것이 클래스 LinkedListIterator이다. 이터레이터 클래스는 다음과 같이 구현한다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;__next__( ) 함수를 갖는 이터레이터를 반환하는 __iter__( ) 함수를 구현한다.&lt;/li&gt;
&lt;li&gt;__next__( ) 함수는 다음 원소를 꺼내 반환한다. 반환하는 원소가 없으면 StopIteration 예외 처리를 내보낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 프로그램의 이터레이터는 주목 포인터 current를 업데이트 하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;포인터로 연결 리스트 프로그램 만들기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 프로그램은 지금까지 설명한 연결 리스트 클래스인 LinkedList를 사용하는 프로그램이다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4; text-align: start;&quot;&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;# 포인터를 이용한 연결 리스트 클래스 LinkedList 사용하기

from enum import Enum
from linked_list import LinkedList

Menu = Enum('Menu', ['머리에노드삽입', '꼬리에노드삽입', '머리노드삭제',
                     '꼬리노드삭제', '주목노드출력', '주목노드이동',
                     '주목노드삭제', '모든노드삭제', '검색', '멤버십판단',
                     '모든노드출력', '스캔', '종료'])

def select_Menu() -&amp;gt; Menu:
    &quot;&quot;&quot;메뉴 선택&quot;&quot;&quot;
    s = [f'({m.value}){m.name}' for m in Menu]
    while True:
        print(*s, sep = '  ', end='')
        n = int(input(': '))
        if 1 &amp;lt;= n &amp;lt;= len(Menu):
            return Menu(n)

lst = LinkedList()                  # 연결 리스트를 생성

while True:
    menu = select_Menu()            # 메뉴를 선택

    if menu == Menu.머리에노드삽입:     # 맨 앞에 노드를 삽입
        lst.add_first(int(input('머리 노드에 넣을 값을 입력하세요.: ')))

    elif menu == Menu.꼬리에노드삽입:   # 맨 뒤에 노드를 삽입
        lst.add_last(int(input('꼬리 노드에 넣을 값을 입력하세요.: ')))

    elif menu == Menu.머리노드삭제:     # 맨 앞에 노드를 삭제
        lst.remove_first()

    elif menu == Menu.꼬리노드삭제:     # 맨 뒤에 노드를 삭제
        lst.remove_last()

    elif menu == Menu.주목노드출력:     # 주목 노드를 출력
        lst.print_current_node()

    elif menu == Menu.주목노드이동:     # 주목 노드를 한 칸 뒤로 이동
        lst.next()

    elif menu == Menu.주목노드삭제:     # 주목 노드를 삭제
        lst.remove_current_node()

    elif menu == Menu.모든노드삭제:     # 모든 노드를 삭제
        lst.clear()

    elif menu == Menu.검색:          # 노드 검색
        pos = lst.search(int(input('검색할 값을 입력하세요.: ')))
        if pos &amp;gt;= 0:
            print(f'그 값의 데이터는 {pos + 1}번째에 있습니다.')
        else:
            print('해당하는 데이터가 없습니다.')

    elif menu == Menu.멤버십판단:      # 멤버십을 판단
        print('그 값의 데이터는 포함되어'
              +(' 있습니다.' if int(input('판단할 값을 입력하세요.: ')) in lst else ''
              '있지 않습니다.'))

    elif menu == Menu.모든노드출력:    # 모든 노드를 출력
        lst.print()

    elif menu == Menu.스캔:          # 모든 노드를 스캔
        for e in lst:
            print(e)

    else:                           # 종료
        break&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과&lt;/p&gt;
&lt;pre id=&quot;code_1727944337272&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(1)머리에노드삽입  (2)꼬리에노드삽입  (3)머리노드삭제  (4)꼬리노드삭제  (5)주목노드출력  (6)주목노드이동  (7)주목노드삭제  (8)모든노드삭제  (9)검색  (10)멤버십판단  (11)모든노드출력  (12)스캔  (13)종료: 1
머리 노드에 넣을 값을 입력하세요.: 1
(1)머리에노드삽입  (2)꼬리에노드삽입  (3)머리노드삭제  (4)꼬리노드삭제  (5)주목노드출력  (6)주목노드이동  (7)주목노드삭제  (8)모든노드삭제  (9)검색  (10)멤버십판단  (11)모든노드출력  (12)스캔  (13)종료: 2
꼬리 노드에 넣을 값을 입력하세요.: 5
(1)머리에노드삽입  (2)꼬리에노드삽입  (3)머리노드삭제  (4)꼬리노드삭제  (5)주목노드출력  (6)주목노드이동  (7)주목노드삭제  (8)모든노드삭제  (9)검색  (10)멤버십판단  (11)모든노드출력  (12)스캔  (13)종료: 1
머리 노드에 넣을 값을 입력하세요.: 10
(1)머리에노드삽입  (2)꼬리에노드삽입  (3)머리노드삭제  (4)꼬리노드삭제  (5)주목노드출력  (6)주목노드이동  (7)주목노드삭제  (8)모든노드삭제  (9)검색  (10)멤버십판단  (11)모든노드출력  (12)스캔  (13)종료: 2
꼬리 노드에 넣을 값을 입력하세요.: 12
(1)머리에노드삽입  (2)꼬리에노드삽입  (3)머리노드삭제  (4)꼬리노드삭제  (5)주목노드출력  (6)주목노드이동  (7)주목노드삭제  (8)모든노드삭제  (9)검색  (10)멤버십판단  (11)모든노드출력  (12)스캔  (13)종료: 1
머리 노드에 넣을 값을 입력하세요.: 14
(1)머리에노드삽입  (2)꼬리에노드삽입  (3)머리노드삭제  (4)꼬리노드삭제  (5)주목노드출력  (6)주목노드이동  (7)주목노드삭제  (8)모든노드삭제  (9)검색  (10)멤버십판단  (11)모든노드출력  (12)스캔  (13)종료: 4
(1)머리에노드삽입  (2)꼬리에노드삽입  (3)머리노드삭제  (4)꼬리노드삭제  (5)주목노드출력  (6)주목노드이동  (7)주목노드삭제  (8)모든노드삭제  (9)검색  (10)멤버십판단  (11)모든노드출력  (12)스캔  (13)종료: 9
검색할 값을 입력하세요.: 12
해당하는 데이터가 없습니다.
(1)머리에노드삽입  (2)꼬리에노드삽입  (3)머리노드삭제  (4)꼬리노드삭제  (5)주목노드출력  (6)주목노드이동  (7)주목노드삭제  (8)모든노드삭제  (9)검색  (10)멤버십판단  (11)모든노드출력  (12)스캔  (13)종료: 9
검색할 값을 입력하세요.: 10
그 값의 데이터는 2번째에 있습니다.
(1)머리에노드삽입  (2)꼬리에노드삽입  (3)머리노드삭제  (4)꼬리노드삭제  (5)주목노드출력  (6)주목노드이동  (7)주목노드삭제  (8)모든노드삭제  (9)검색  (10)멤버십판단  (11)모든노드출력  (12)스캔  (13)종료: 5
10
(1)머리에노드삽입  (2)꼬리에노드삽입  (3)머리노드삭제  (4)꼬리노드삭제  (5)주목노드출력  (6)주목노드이동  (7)주목노드삭제  (8)모든노드삭제  (9)검색  (10)멤버십판단  (11)모든노드출력  (12)스캔  (13)종료: 11
14
10
1
5
(1)머리에노드삽입  (2)꼬리에노드삽입  (3)머리노드삭제  (4)꼬리노드삭제  (5)주목노드출력  (6)주목노드이동  (7)주목노드삭제  (8)모든노드삭제  (9)검색  (10)멤버십판단  (11)모든노드출력  (12)스캔  (13)종료: 9
검색할 값을 입력하세요.: 1
그 값의 데이터는 3번째에 있습니다.
(1)머리에노드삽입  (2)꼬리에노드삽입  (3)머리노드삭제  (4)꼬리노드삭제  (5)주목노드출력  (6)주목노드이동  (7)주목노드삭제  (8)모든노드삭제  (9)검색  (10)멤버십판단  (11)모든노드출력  (12)스캔  (13)종료: 7
(1)머리에노드삽입  (2)꼬리에노드삽입  (3)머리노드삭제  (4)꼬리노드삭제  (5)주목노드출력  (6)주목노드이동  (7)주목노드삭제  (8)모든노드삭제  (9)검색  (10)멤버십판단  (11)모든노드출력  (12)스캔  (13)종료: 3
(1)머리에노드삽입  (2)꼬리에노드삽입  (3)머리노드삭제  (4)꼬리노드삭제  (5)주목노드출력  (6)주목노드이동  (7)주목노드삭제  (8)모든노드삭제  (9)검색  (10)멤버십판단  (11)모든노드출력  (12)스캔  (13)종료: 11
10
5
(1)머리에노드삽입  (2)꼬리에노드삽입  (3)머리노드삭제  (4)꼬리노드삭제  (5)주목노드출력  (6)주목노드이동  (7)주목노드삭제  (8)모든노드삭제  (9)검색  (10)멤버십판단  (11)모든노드출력  (12)스캔  (13)종료: 9
검색할 값을 입력하세요.: 10
그 값의 데이터는 1번째에 있습니다.
(1)머리에노드삽입  (2)꼬리에노드삽입  (3)머리노드삭제  (4)꼬리노드삭제  (5)주목노드출력  (6)주목노드이동  (7)주목노드삭제  (8)모든노드삭제  (9)검색  (10)멤버십판단  (11)모든노드출력  (12)스캔  (13)종료: 6
(1)머리에노드삽입  (2)꼬리에노드삽입  (3)머리노드삭제  (4)꼬리노드삭제  (5)주목노드출력  (6)주목노드이동  (7)주목노드삭제  (8)모든노드삭제  (9)검색  (10)멤버십판단  (11)모든노드출력  (12)스캔  (13)종료: 5
5
(1)머리에노드삽입  (2)꼬리에노드삽입  (3)머리노드삭제  (4)꼬리노드삭제  (5)주목노드출력  (6)주목노드이동  (7)주목노드삭제  (8)모든노드삭제  (9)검색  (10)멤버십판단  (11)모든노드출력  (12)스캔  (13)종료: 10
판단할 값을 입력하세요.: 7
그 값의 데이터는 포함되어있지 않습니다.
(1)머리에노드삽입  (2)꼬리에노드삽입  (3)머리노드삭제  (4)꼬리노드삭제  (5)주목노드출력  (6)주목노드이동  (7)주목노드삭제  (8)모든노드삭제  (9)검색  (10)멤버십판단  (11)모든노드출력  (12)스캔  (13)종료: 12
10
5
(1)머리에노드삽입  (2)꼬리에노드삽입  (3)머리노드삭제  (4)꼬리노드삭제  (5)주목노드출력  (6)주목노드이동  (7)주목노드삭제  (8)모든노드삭제  (9)검색  (10)멤버십판단  (11)모든노드출력  (12)스캔  (13)종료: 13

Process finished with exit code 0&lt;/code&gt;&lt;/pre&gt;</description>
      <category>자료구조, 알고리즘 입문</category>
      <author>sungw00</author>
      <guid isPermaLink="true">https://vibeee.tistory.com/55</guid>
      <comments>https://vibeee.tistory.com/55#entry55comment</comments>
      <pubDate>Wed, 9 Oct 2024 00:01:04 +0900</pubDate>
    </item>
    <item>
      <title>[정렬 알고리즘] 셸 정렬</title>
      <link>https://vibeee.tistory.com/291</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;셸 정렬은 단순 삽입 정렬의 장점은 살리고 단점은 보완하여 더 빠르게 정렬하는 알고리즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단순 삽입 정렬의 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 배열에 단순 삽입 정렬을 적용해보자&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;63&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4huiV/btsJw6v4HIV/0dDa8A09lbFdI3KvjwMJ2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4huiV/btsJw6v4HIV/0dDa8A09lbFdI3KvjwMJ2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4huiV/btsJw6v4HIV/0dDa8A09lbFdI3KvjwMJ2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4huiV%2FbtsJw6v4HIV%2F0dDa8A09lbFdI3KvjwMJ2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;42&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;63&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 원소부터 주목하여 2, 3, 4, 5를 순서대로 선택하며 정렬한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지는 이미 정렬을 마친 상태이므로 원소의 이동(값의 대입)은 발생하지 않는다. 이 단계까지는 아주 빠르게 완료한다. 그러나 아래 그림과 같이 여섯 번째 원소인 0을 삽입 정렬하려면 총 6번에 걸쳐 원소를 이동(대입)해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;984&quot; data-origin-height=&quot;770&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mVR0H/btsJyMpu4FQ/gA6pwU7ekY7bkH61nPDZN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mVR0H/btsJyMpu4FQ/gA6pwU7ekY7bkH61nPDZN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mVR0H/btsJyMpu4FQ/gA6pwU7ekY7bkH61nPDZN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmVR0H%2FbtsJyMpu4FQ%2FgA6pwU7ekY7bkH61nPDZN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;618&quot; height=&quot;484&quot; data-origin-width=&quot;984&quot; data-origin-height=&quot;770&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순 삽입 정렬은 다음과 같은 특징이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점: 이미 정렬을 마쳤거나 정렬이 거의 끝나가는 상태에서는 속도가 아주 빠르다.&lt;/li&gt;
&lt;li&gt;단점: 삽입할 위치가 멀리 떨어져 있으면 이동 횟수가 많아진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;셸 정렬 알아보기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순 삽입 정렬의 장점을 살리면서 단점을 보완한 것이 도널드 L.셸이 고안한 셸 정렬 알고리즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셸 정렬은 먼저 정렬할 배열의 원소를 그룹으로 나눠 각 그룹별로 정렬을 수행한다. 그 후 정렬된 그룹을 합치는 작업을 반복하여 원소의 이동 횟수를 줄이는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림에서의 배열을 예로 들어 알고리즘을 이해해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 서로 4칸씩 떨어진 원소를 꺼내어 (8, 7), (1, 6), (4, 3), (2, 5)의 4개 그룹으로 나누고 그룹별로 각각 정렬한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 ①은 (8, 7)을 정렬하여 (7, 8)로, ②는 (1, 6)을 정렬하여 (1, 6)으로, ③은 (4, 3)을 정렬하여 (3, 4)로, ④는 (2, 5)를 정렬하여 (2, 5)로 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 서로 4칸 떨어진 원소를 정렬하는 방법을 '4-정렬'이라고 한다. 아직 정렬을 마치진 않았지만 정렬을 거의 마친 상태에 가까워진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;1410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAuut6/btsJyAW6LJo/1T1ZuV7D8fVsXrH1LNuz11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAuut6/btsJyAW6LJo/1T1ZuV7D8fVsXrH1LNuz11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAuut6/btsJyAW6LJo/1T1ZuV7D8fVsXrH1LNuz11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAuut6%2FbtsJyAW6LJo%2F1T1ZuV7D8fVsXrH1LNuz11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1850&quot; height=&quot;1410&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;1410&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이어서 2칸 떨어진 원소를 모두 꺼내 (7, 3, 8, 4), (1, 2, 6, 5)의 두 그룹으로 나누고 '2-정렬'을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬을 마치고 나면 아래 그림과 같이 (3, 4, 7, 8)과 (1, 2, 5, 6)이 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;928&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MVhID/btsJxysczAn/ddXZMce429kKSMesCRpU41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MVhID/btsJxysczAn/ddXZMce429kKSMesCRpU41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MVhID/btsJxysczAn/ddXZMce429kKSMesCRpU41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMVhID%2FbtsJxysczAn%2FddXZMce429kKSMesCRpU41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1850&quot; height=&quot;928&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;928&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해서 얻은 배열은 좀 더 정렬된 상태에 가까워진다. 마지막으로 '1-정렬'을 적용하여 1칸 떨어진 배열, 즉 배열 전체에 적용하면 정렬이 완료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림을 통해 셸 정렬의 전체 흐름을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셸 정렬 과정에서 수행하는 각각의 정렬을 h-정렬이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림은 h값을 4, 2, 1로 감소시키면서 정렬을 총 7번 수행하여 정렬을 완료한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2개 원소에서 4-정렬을 수행(4개 그룹, 4번)&lt;/li&gt;
&lt;li&gt;4개 원소에서 2-정렬을 수행(2개 그룹, 2번)&lt;/li&gt;
&lt;li&gt;8개 원소에서 1-정렬을 수행(1개 그룹, 1번)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;rarr; 총 7번 정렬&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1369&quot; data-origin-height=&quot;485&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u5tOA/btsJyJ7oK1Q/U9HpTKlWz402EE7EuuXE2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u5tOA/btsJyJ7oK1Q/U9HpTKlWz402EE7EuuXE2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u5tOA/btsJyJ7oK1Q/U9HpTKlWz402EE7EuuXE2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu5tOA%2FbtsJyJ7oK1Q%2FU9HpTKlWz402EE7EuuXE2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;711&quot; height=&quot;252&quot; data-origin-width=&quot;1369&quot; data-origin-height=&quot;485&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a 배열을 바로 단순 삽입 정렬하지 않고 '4-정렬'과 '2-정렬'을 먼저 수행하여 정렬을 거의 마친 상태인 c를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 마지막으로 단순 삽입 정렬을 한 번 수행하여 정렬을 완료하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇듯 셸 정렬은 단순 삽입 정렬의 장점을 살리고 단점을 보완하기 위해 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬 횟수는 늘어나지만 전체적으로 원소의 이동 횟수가 줄어들어 효율적이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 셸 정렬을 수행하는 프로그램이다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;# 셸 정렬 알고리즘 구현하기

from typing import MutableSequence

def shell_sort(a: MutableSequence) -&amp;gt; None:
    &quot;&quot;&quot;셸 정렬&quot;&quot;&quot;
    n = len(a)
    h = n // 2
    while h &amp;gt; 0:
        for i in range(h, n):
            j = i - h
            tmp = a[i]
            while j &amp;gt;= 0 and a[j] &amp;gt; tmp:
                a[j + h] = a[j]
                j -= h
            a[j + h] = tmp
        h //= 2

if __name__ == '__main__':
    print('셸 정렬을 수행합니다.')
    num = int(input('원소 수를 입력하세요.: '))
    x = [None] * num    # 원소 수가 num인 배열을 생성

    for i in range(num):
        x[i] = int(input(f'x[{i}]: '))

    shell_sort(x)   # 배열 x를 셸 정렬

    print('오름차순으로 정렬했습니다.')
    for i in range(num):
        print(f'x[{i}] = {x[i]}')&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과&lt;/p&gt;
&lt;pre id=&quot;code_1725975733654&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;셸 정렬을 수행합니다.
원소 수를 입력하세요.: 8
x[0]: 8
x[1]: 1
x[2]: 4
x[3]: 2
x[4]: 7
x[5]: 6
x[6]: 3
x[7]: 5
오름차순으로 정렬했습니다.
x[0] = 1
x[1] = 2
x[2] = 3
x[3] = 4
x[4] = 5
x[5] = 6
x[6] = 7
x[7] = 8

Process finished with exit code 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 프로그램의 while 문 내부 코드는 단순 삽입 정렬을 수행하는 과정으로 단순 삽입 정렬의 코드와 거의 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 점이 있다면 주목하는 원소와 비교하는 원소가 서로 이웃하지 않고 h개만큼 떨어져 있다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 h의 초깃값은 n // 2로 구한다(전체 배열의 절반값이기 때문이다).&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 while문을 반복할 때마다 다시 2로 나눈 값으로 업데이트 한다. 즉, h는 다음과 같이 변화한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원소 수가 8이면 4 &amp;rarr; 2 &amp;rarr; 1&lt;/li&gt;
&lt;li&gt;원소 수가 7이면 3 &amp;rarr; 1&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 h값을 선택하는 방법을 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;h값의 선택&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 원소 수인 n값이 8이라면 h값을 다음과 같이 변화시킨다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;h = 4 &amp;rarr; 2 &amp;rarr; 1&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;h값은 n부터 감소하다가 마지막에는 1이 된다. 그렇다면 h값을 어떤 수열로 감소시키면 효율적인 정렬을 할 수 있을지 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림을 보면서 배열 그룹을 나누는 과정부터 다시 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1103&quot; data-origin-height=&quot;738&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tnhy5/btsJF9euseL/vQkhg5rDOhO2Ajj08hzM4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tnhy5/btsJF9euseL/vQkhg5rDOhO2Ajj08hzM4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tnhy5/btsJF9euseL/vQkhg5rDOhO2Ajj08hzM4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftnhy5%2FbtsJF9euseL%2FvQkhg5rDOhO2Ajj08hzM4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;666&quot; height=&quot;446&quot; data-origin-width=&quot;1103&quot; data-origin-height=&quot;738&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a를 학생 8명의 점수라고 가정했을 때 그림 b처럼 학생 2명씩 4개 그룹으로 나누어 정렬할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 다음 c처럼 학생 4명씩 2개 그룹으로 나누어 다시 정렬한다. 여기서 b의 두 그룹을 합쳐서 c가 되는 과정을 잘 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림에서 파란색 그룹 ((8, 7), (4, 3))과 검은색 그룹((1, 6), (2, 5))은 섞이지 않는다. 그런데 이렇게 두 그룹이 섞이지 않은 상태에서 c로 함치면 다시 처음 단계인 a와 같아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 애써 그룹으로 나누어서 정렬했지만 충분히 그 기능을 하지 못한다는 것을 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하려면 h값이 서로 배수가 되지 않도록 해야 한다. 그러면 원소가 충분히 뒤섞이므로 효율 좋은 정렬을 기대할 수 있다. 다음 수열을 사용하면 셸 정렬 알고리즘을 간단하게 만들 수 있고 효율적인 정렬도 할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;h = &amp;hellip; &amp;rarr; 121 &amp;rarr; 40 &amp;rarr; 13 &amp;rarr; 4 &amp;rarr; 1&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 수열을 거꾸로 살펴보면 1부터 시작하여 3개한 값에 1을 더하고 있다. 하지만 h의 초깃값이 지나치게 크면 효과가 없다. 따라서 배열의 원소 수인 n을 9로 나누었을 때 그 몫을 넘지 않도록 정해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 프로그램은 이 방식의 수열을 사용하여 셸 정렬을 수행하는 프로그램이다.&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;# 셸 정렬 알고리즘 구현하기(h * 3 + 1의 수열 사용)

from typing import MutableSequence

def shell_sort(a: MutableSequence) -&amp;gt; None:
    &quot;&quot;&quot;셸 정렬(h * 3 + 1의 수열 사용)&quot;&quot;&quot;
    n = len(a)
    h = 1

    while h &amp;lt; n // 9:
        h = h * 3 + 1

    while h &amp;gt; 0:
        for i in range(h, n):
            j = i - h
            tmp = a[i]
            while j &amp;gt;= 0 and a[j] &amp;gt; tmp:
                a[j + h] = a[j]
                j -= h
            a[j + h] = tmp
        h //= 3

if __name__ == '__main__':
    print('셸 정렬을 수행합니다(h * 3 + 1의 수열 사용).')
    num = int(input('원소 수를 입력하세요.: '))
    x = [None] * num    # 원소 수가 num인 배열을 생성

    for i in range(num):
        x[i] = int(input(f'x[{i}]: '))

    shell_sort(x)   # 배열 x를 셸 정렬

    print(f'오름차순으로 정렬했습니다.')
    for i in range(num):
        print(f'x[{i}] = {x[i]}')&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과&lt;/p&gt;
&lt;pre id=&quot;code_1726752915019&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;셸 정렬을 수행합니다(h * 3 + 1의 수열 사용).
원소 수를 입력하세요.: 8
x[0]: 8
x[1]: 1
x[2]: 4
x[3]: 2
x[4]: 7
x[5]: 6
x[6]: 3
x[7]: 5
오름차순으로 정렬했습니다.
x[0] = 1
x[1] = 2
x[2] = 3
x[3] = 4
x[4] = 5
x[5] = 6
x[6] = 7
x[7] = 8

Process finished with exit code 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 프로그램 중 첫번째 while문에서는 h의 초깃값을 구한다. 1부터 시작해서 h * 3 + 1의 수열을 사용하는 작업을 반복하지만 n // 9를 넘지 않는 최댓값을 h에 대입한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 반복문은 h값이 변하면서 h값을 3으로 나누는 작업을 반복해서 결국에 h값은 1이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셸 정렬의 시간 복잡도는 O(n^1.25)이고 단순 정렬의 시간 복잡도인 O(n^2)보다 매우 빠르다. 그러나 셸 정렬 알고리즘은 이웃하지 않고 떨어져 있는 원소를 서로 교환하므로 안정적이지 않다.&lt;/p&gt;</description>
      <author>sungw00</author>
      <guid isPermaLink="true">https://vibeee.tistory.com/291</guid>
      <comments>https://vibeee.tistory.com/291#entry291comment</comments>
      <pubDate>Thu, 3 Oct 2024 19:17:17 +0900</pubDate>
    </item>
    <item>
      <title>[문자열 검색 알고리즘] 보이어 무어(BM)법</title>
      <link>https://vibeee.tistory.com/54</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;보이어 무어법의 특징&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 알고리즘을 고안한 보이어와 무어의 이름을 따서 BM법이라고도 함&lt;/li&gt;
&lt;li&gt;KMP법보다 효율적이어서 실제 문자열 검색에서 주로 사용&lt;/li&gt;
&lt;li&gt;패턴의 끝 문자에서 시작하여 앞쪽을 향해 검사를 수행&lt;/li&gt;
&lt;li&gt;일치하지 않는 문자를 발견하면 미리 준비한 표를 바탕으로 패턴이 이동하는 값을 결정함&lt;/li&gt;
&lt;li&gt;시간복잡도는 최악의 경우라도 O(n)이고 평균 O(n / m)이다. 보이어 무어법은 배열을 1개만 사용해도 충분히 빠르다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;보이어 무어법의 진행 과정&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 보이어 무어법으로 텍스트 'ABCXDEZCABACABAC' 에서 패턴 'ABAC' 를 검색하는 과정을 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1960&quot; data-origin-height=&quot;676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/de5e1V/btrH31wNZe1/Bdg5pImNsCy1eRnpPXekVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/de5e1V/btrH31wNZe1/Bdg5pImNsCy1eRnpPXekVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/de5e1V/btrH31wNZe1/Bdg5pImNsCy1eRnpPXekVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fde5e1V%2FbtrH31wNZe1%2FBdg5pImNsCy1eRnpPXekVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1960&quot; height=&quot;676&quot; data-origin-width=&quot;1960&quot; data-origin-height=&quot;676&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 위 그림처럼 텍스트와 패턴의 첫 문자를 위 아래로 나란히 놓고 패턴의 마지막 문자 'C'에 주목한다. 같은 위치에 있는 텍스트의 'X'는 패턴 안에 포함되어 있지 않다. 따라서 패턴을 이동해도 텍스트의 문자 'X'와 패턴의 문자가 일치하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림처럼 패턴에 포함되지 않는 문자를 텍스트에서 발견하면 그 위치까지는 건너뛸 수 있다. 그러므로 패턴을 비교하는 1~4번째 과정을 생략하고 패턴을 오른쪽으로 한번에 4칸 밀면 다음 그림과 같이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1654&quot; data-origin-height=&quot;590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WdzH8/btrIbL0mByk/TmUuCs1VaFTdCk1by5Ckd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WdzH8/btrIbL0mByk/TmUuCs1VaFTdCk1by5Ckd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WdzH8/btrIbL0mByk/TmUuCs1VaFTdCk1by5Ckd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWdzH8%2FbtrIbL0mByk%2FTmUuCs1VaFTdCk1by5Ckd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1654&quot; height=&quot;590&quot; data-origin-width=&quot;1654&quot; data-origin-height=&quot;590&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 패턴의 마지막 문자 'C'를 텍스트와 비교하면 일치하므로 1칸 앞의 문자 'A'로 되돌아가서 위 그림과 같은 상태로 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 위 그림에서 패턴의 문자 'A'는 텍스트의 문자 'Z'와 일치하지 않고, 패턴을 1칸 또는 2칸 밀어도 텍스트의 문자 'Z'와 패턴의 문자는 일치하지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 패턴을 한 번에 3칸 밀어 다음 그림과 같은 상태로 만든다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1950&quot; data-origin-height=&quot;672&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyX07D/btrH2nsINaB/MlI5W8J1qrvlfiPF9Kfie0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyX07D/btrH2nsINaB/MlI5W8J1qrvlfiPF9Kfie0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyX07D/btrH2nsINaB/MlI5W8J1qrvlfiPF9Kfie0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyX07D%2FbtrH2nsINaB%2FMlI5W8J1qrvlfiPF9Kfie0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1950&quot; height=&quot;672&quot; data-origin-width=&quot;1950&quot; data-origin-height=&quot;672&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림에서 패턴의 마지막 문자 'C'는 텍스트 문자 'A'와 일치하지 않는다. 그런데 문자 'A'는 패턴의 1번째와 3번째에 포함되어 있다. 그래서 패턴의 두번째 그림과 마찬가지로 뒤쪽에 있는 'A'가 위아래로 겹치도록 패턴을 오른쪽으로 1칸 밀어내서 아래 그림의 상태와 같이 만들 수 있다. 하지만 위 그림에서 패턴 4번째의 그림과 같이 패턴 앞쪽에 있는 'A'가 위아래로 겹치도록 오른쪽으로 한번에 3칸만큼 밀어내면 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJgoXG/btrH7QVbFNA/KuBOJHoaSL4wxNhSqfT1Gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJgoXG/btrH7QVbFNA/KuBOJHoaSL4wxNhSqfT1Gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJgoXG/btrH7QVbFNA/KuBOJHoaSL4wxNhSqfT1Gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJgoXG%2FbtrH7QVbFNA%2FKuBOJHoaSL4wxNhSqfT1Gk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1308&quot; height=&quot;400&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 위의 그림처럼 맨 끝부터 문자를 차례로 비교하면 모든 문자가 일치하므로 검색에 성공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보이어 무어법 알고리즘도 KMP법과 마찬가지로 각각의 문자를 만났을 때 패턴의 이동할 크기를 저장하는 표(건너뛰기 표)를 미리 만들어 둘 필요가 있다. 패턴 문자열의 길이가 n일 때 이동할 크기(이동량)는 다음과 같이 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;패턴에 포함되지 않는 문자를 만난 경우&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;패턴 이동량이 곧 n이다. 위 과정에서와 마찬가지로 'X'는 패턴에 포함되지 않으므로 4문자만큼 밀어낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;패턴에 포함되는 문자를 만난 경우&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마지막에 나오는 위치의 인덱스가 k이면 이동량은 n - k - 1이다. 위 과정에서 살펴본 그림처럼 'A'는 패턴 안의 두 곳에 있다. 패턴을 오른쪽으로 1칸 밀어낸다.&lt;/li&gt;
&lt;li&gt;같은 문자가 패턴 안에 중복해서 존재하지 않으면 패턴의 맨 끝 문자의 이동량은 n이다. 예를 들어 'ABAC'의 'C'를 만나면 이동할 필요가 없으므로 이동량은 n이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 과정을 진행할 때 건너뛰기 표는 다음과 같이 완성된다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 100%;&quot;&gt;&lt;b&gt;텍스트:&lt;/b&gt; &quot;ABACXDEZCABACABAC&quot;&lt;br /&gt;&lt;b&gt;패턴:&lt;/b&gt; &quot;ABAC&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYifSy/btrH0OYSTyc/4zFMkVyHD50Dh7kPDnaVRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYifSy/btrH0OYSTyc/4zFMkVyHD50Dh7kPDnaVRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYifSy/btrH0OYSTyc/4zFMkVyHD50Dh7kPDnaVRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYifSy%2FbtrH0OYSTyc%2F4zFMkVyHD50Dh7kPDnaVRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1286&quot; height=&quot;328&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 보이어 무어법을 구현한 프로그램이다. bm_match( ) 함수가 전달받는 인수와 반환값은 브루트 포스법의 bf_match( ) 함수와 KMP 법의 kmp_match( ) 함수와 같다. 패턴 안에 존재할 수 있는 모든 문자의 이동량을 계산해야 하므로 이 건너뛰기 표에서 사용하는 원소는 256개이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;여기에서는 배열 하나를 사용하여 보이어 무어법을 간략하게 나타냈다. 원래의 보이어 무어법은 배열 2개를 사용해서 검사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;보이어 무어법 코드&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;# 보이어 무어법으로 문자열 검색하기(문자열 길이는 0~255개)

def bm_match(txt: str, pat: str) -&amp;gt; int:
    &quot;&quot;&quot;보이어 무어법으로 문자열 검색&quot;&quot;&quot;
    skip = [None] * 256 # 건너뛰기 표

    # 건너뛰기 표 만들기
    for pt in range(256):
        skip[pt] = len(pat)
    for pt in range(len(pat)):
        skip[ord(pat[pt])] = len(pat) - pt - 1

    # 검색하기
    while pt &amp;lt; len(txt):
        pp = len(pat) - 1
        while txt[pt] == pat[pp]:
            if pp == 0:
                return pt
            pt -= 1
            pp -= 1
        pt += skip[ord(txt[pt])] if skip[ord(txt[pt])] &amp;gt; len(pat) - pp \
            else len(pat) - pp

    return -1

if __name__ == &quot;__main__&quot;:
    s1 = input(&quot;텍스트를 입력하세요.: &quot;) # 텍스트용 문자열
    s2 = input(&quot;패턴을 입력하세요.: &quot;)  # 패턴용 문자열

    idx = bm_match(s1, s2)  # 문자열 s1 ~ s2를 보이어 무어법으로 검색

    if idx == -1:
        print(&quot;텍스트 안에 패턴이 존재하지 않습니다.&quot;)
    else:
        print(f&quot;{idx + 1}번째 문자가 일치합니다.&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과&lt;/p&gt;
&lt;pre id=&quot;code_1727530804464&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;텍스트를 입력하세요.: ABABCDEFGHA
패턴을 입력하세요.: ABC
3번째 문자가 일치합니다.

Process finished with exit code 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;보이어 무어법 코드의 진행 과정&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;건너뛰기 표 만들기(찾는 문자열의 범위만큼)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;예를 들면 영문 대문자 또는 소문자만 있으면 26개, 영문 대문자와 소문자는 52개, ASCII코드값만큼이면 256개&lt;/li&gt;
&lt;li&gt;건너뛰기 표를 패턴의 길이로 전부 초기화&lt;/li&gt;
&lt;li&gt;찾을 대상이 되는 패턴의 문자열의 대상 배열만 len(pat) - pt - 1로 초기화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;검색하기(pt가 txt의 길이보다 작은 동안 = &lt;u&gt;검색이 끝나지 않을 조건&lt;/u&gt;인 동안)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;패턴의 가장 끝부터 비교하기 때문에 pp는 len(pat) - 1부터 시작&lt;/li&gt;
&lt;li&gt;문자가 일치하는 경우, 즉 txt[pt] == pat[pp]일 때&amp;nbsp;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pp가 가장 첫 문자[0]를 가리키고 있다면 반복을 끝내고 반환&lt;/li&gt;
&lt;li&gt;아니면 pt -= 1, pp -= 1해서 한칸씩 앞의 문자와 비교&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;만약 건너뛰기 표의 [ord(txt[pt])]가 len(pat) - pp보다 크면 pt += skip_table[ord(txt[pt])]를 리턴하고, 아니면 len(pat) - pp를 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;찾는 값이 없다면 -1을 반환&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문자열 검색 알고리즘의 시간 복잡도&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;지금까지 구현한 3가지 문자열 검색 알고리즘의 시간 복잡도를 살펴보자.(텍스트의 길이 = n, 패턴의 길이 = m)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;브루트 포스법&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 알고리즘의 시간 복잡도는 O(mn)이지만 일부러 꾸며낸 패턴이 아니라면 O(n)이 된다고 알려져 있다. 단순한 알고리즘이지만 실제로는 아주 빠르게 동작한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KMP법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 알고리즘의 시간 복잡도는 최악의 경우에도 O(n)이다. 다만 처리하기 복잡하고 패턴 안에 반복이 없으면 효율은 좋지 않다. 그러나 검색 과정에서 주목하는 곳을 앞으로 되돌릴 필요가 전혀 없으므로 파일을 차례로 읽어 들이면서 검색할 때 사용하면 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보이어 무어법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 알고리즘의 시간 복잡도는 최악의 경우라도 O(n)이고 평균 O(n / m)이다. 위 코드에서는 배열을 1개만 사용했지만 배열 2개로 알고리즘을 구현하면 KMP법과 마찬가지로 배열을 만드는 데 복잡한 처리 과정이 필요하므로 효율성이 떨어진다. 보이어 무어법은 배열을 1개만 사용해도 충분히 빠르다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 파이썬에서 문자열 검색을 하려면 표준 라이브러리를 사용하는 것을 추천한다. 만약 표준 라이브러리를 사용하지 않는다면 보이어 무어법(또는 개선한 방법)이나 상황에 따라서 브루트 포스법을 사용하는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문자 코드를 다루는 ord( ) 함수와 chr( ) 함수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 사용한 내장 함수 ord( )는 단일한 문자를 전달받아 그 문자의 유니코드 코드 포인트를 정수로 반환한다. 예를 들어 ord('a')는 정수 97을 반환한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 이 함수의 변환을 거꾸로 수행하는 내장 함수는 chr( )이다. 예를 들어 chr(97)은 문자 'a'를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;유니코드 코드 포인트는 유니코드 체계에서 문자마다 부여한 고유한 숫자의 값을 뜻한다.&lt;/p&gt;</description>
      <category>자료구조, 알고리즘 입문/문자열 검색 알고리즘</category>
      <author>sungw00</author>
      <guid isPermaLink="true">https://vibeee.tistory.com/54</guid>
      <comments>https://vibeee.tistory.com/54#entry54comment</comments>
      <pubDate>Sat, 28 Sep 2024 22:47:41 +0900</pubDate>
    </item>
    <item>
      <title>[문자열 검색 알고리즘] KMP법</title>
      <link>https://vibeee.tistory.com/53</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;KMP법이란?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Knuth-Morris-Pratt법의 줄임말로 이 알고리즘을 고안한 크누스, 모리스, 프래트의 이름에서 따온 용어이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브루트 포스법은 일치하지 않는 문자를 만나면 다시 패턴의 첫 문자부터 검사를 수행하지만, KMP법은 검사 결과를 효율적으로 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;KMP법 알아보기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브루트 포스법은 일치하지 않는 문자를 만나면 이전 단계에서 검사했던 결과를 버리고 패턴의 첫 문자부터 다시 검색한다. 하지만 KMP법은 검사했던 결과를 버리지 않고 효율적으로 활용하는 알고리즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트 'ZABCABXACCADEF'에서 패턴 'ABCABC'를 검색할 때 KMP법 알고리즘을 생각해보면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-07-21 오후 2.54.35.png&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yOqGK/btrHPOqq3qu/LeKNMTpn8LFP584xqHbc1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yOqGK/btrHPOqq3qu/LeKNMTpn8LFP584xqHbc1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yOqGK/btrHPOqq3qu/LeKNMTpn8LFP584xqHbc1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyOqGK%2FbtrHPOqq3qu%2FLeKNMTpn8LFP584xqHbc1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;776&quot; height=&quot;314&quot; data-filename=&quot;스크린샷 2022-07-21 오후 2.54.35.png&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;먼저 위의 그림처럼 텍스트와 패턴의 첫 문자부터 차례로 검사를 수행한다. 텍스트의 첫 문자 'Z'는 패턴에 포함되지 않는 문자이므로 일치하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-07-21 오후 2.58.32.png&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czrK11/btrHPf90oFq/KH0GsbiR7ifKL3OjcMcKs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czrK11/btrHPf90oFq/KH0GsbiR7ifKL3OjcMcKs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czrK11/btrHPf90oFq/KH0GsbiR7ifKL3OjcMcKs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczrK11%2FbtrHPf90oFq%2FKH0GsbiR7ifKL3OjcMcKs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;776&quot; height=&quot;314&quot; data-filename=&quot;스크린샷 2022-07-21 오후 2.58.32.png&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 이제 패턴을 오른쪽으로 1칸 민다. 그럼 위와 같이 비교하는데, 패턴의 앞쪽부터 차례로 검사를 수행해가면 패턴의 마지막 문자 'D'가 텍스트의 'X'와 일치하지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 빨간색 문자로 나타낸 텍스트 안의 'AB'와 패턴안의 'AB'가 일치하는 것에 주목한다. 이 부분을 검사를 마친 위치라고 간주하면, 텍스트에서 'X' 이후 부분이 패턴의 'CABD'와 일치하는지 검사하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 아래 그림처럼 'AB'를 위아래로 나란히 놓고 패턴을 단 한 번에 오른쪽으로 3칸 밀어 &lt;u&gt;3번째 문자 'C'부터 검사를 시작&lt;/u&gt;할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-07-21 오후 3.03.10.png&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c77rPb/btrHTr2qTFA/vKw2OOKVAvPZvS9K8SkN1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c77rPb/btrHTr2qTFA/vKw2OOKVAvPZvS9K8SkN1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c77rPb/btrHTr2qTFA/vKw2OOKVAvPZvS9K8SkN1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc77rPb%2FbtrHTr2qTFA%2FvKw2OOKVAvPZvS9K8SkN1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;776&quot; height=&quot;314&quot; data-filename=&quot;스크린샷 2022-07-21 오후 3.03.10.png&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 KMP법은 텍스트와 패턴 안에서 겹치는 문자열을 찾아내 검사를 다시 시작할 위치를 구하여 패턴의 이동을 되도록이면 크게 하는 알고리즘이다. 그런데 몇 번째 문자부터 검사를 다시 시작할지 패턴을 이동할 때마다 계산한다면 좋은 효율을 기대할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 KMP법은 &lt;b&gt;'몇 번째 문자부터 다시 검색할지' 값을 표로 만들어서&lt;/b&gt; 문제를 해결한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림은 텍스트와 패턴이 불일치한 경우와 몇 번째 문자부터 검사를 다시 시작할지를 보여준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;2530&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1sdnH/btrHTq3yj3F/Llo37jriwOkGrwKfiPZvV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1sdnH/btrHTq3yj3F/Llo37jriwOkGrwKfiPZvV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1sdnH/btrHTq3yj3F/Llo37jriwOkGrwKfiPZvV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1sdnH%2FbtrHTq3yj3F%2FLlo37jriwOkGrwKfiPZvV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1340&quot; height=&quot;2530&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;2530&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;KMP법에서 사용하는 표 만들기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표를 작성할 때는 패턴에서 겹치는 문자열을 찾는다. 이 과정에서도 KMP법과 같은 방법을 적용한다. 패턴의 첫 문자가 일치하지 않으면 패턴을 오른쪽으로 1칸 밀어 첫 문자부터 검사해야 하므로 2번째 문자 이후 부분을 생각한다. 또 패턴과 텍스트를 서로 겹치도록 맞추는 것이 아니라 &lt;b&gt;패턴끼리(즉, 패턴과 패턴을)&lt;/b&gt; 서로 겹치도록 맞추고 검사를 시작할 곳을 계산한다. 표를 작성하는 순서는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRB0dI/btrHOSN3NdL/yBkkuUSbBP2dNc5JnRU6r0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRB0dI/btrHOSN3NdL/yBkkuUSbBP2dNc5JnRU6r0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRB0dI/btrHOSN3NdL/yBkkuUSbBP2dNc5JnRU6r0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRB0dI%2FbtrHOSN3NdL%2FyBkkuUSbBP2dNc5JnRU6r0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;388&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 패턴 'ABCABD' 2개를 위 아래로 나란히 놓고 아래쪽 패턴을 오른쪽으로 1칸 밀어 서로 겹친다. 다음 그림에서 파란색 부분이 일치하지 않으므로 아래쪽 패턴을 이동하면첫 문자부터 검사를 다시 시작해야 한다는 것을 알 수 있다. 그러므로 텍스트에서 2번째 문자 'B'는 다시 시작하는 값을 0으로 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;패턴의 첫 문자 인덱스는 0이다. 그 위치부터 검사를 다시 시작하기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6Xx8s/btrHPJpz4II/7sZo9KimAPcTOYVkrikUbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6Xx8s/btrHPJpz4II/7sZo9KimAPcTOYVkrikUbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6Xx8s/btrHPJpz4II/7sZo9KimAPcTOYVkrikUbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6Xx8s%2FbtrHPJpz4II%2F7sZo9KimAPcTOYVkrikUbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;388&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 패턴을 오른쪽으로 1칸 민다. 마찬가지로 문자가 일치하지 않으므로 텍스트에서 3번째 문자 'C'는 다시 시작하는 값을 0으로 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-07-21 오후 3.43.17.png&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZOXlo/btrHPN6flM8/Ku5QDLWASzZnQlIqw7S7w1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZOXlo/btrHPN6flM8/Ku5QDLWASzZnQlIqw7S7w1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZOXlo/btrHPN6flM8/Ku5QDLWASzZnQlIqw7S7w1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZOXlo%2FbtrHPN6flM8%2FKu5QDLWASzZnQlIqw7S7w1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;494&quot; height=&quot;230&quot; data-filename=&quot;스크린샷 2022-07-21 오후 3.43.17.png&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 패턴을 오른쪽으로 1칸 밀면 'AB'가 일치하는데, 여기서 다음과 같은 사실을 알 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;패턴의 4번째 문자 'A'까지 일치하는 경우&lt;/b&gt;: 패턴 이동 후 'A'를 건너뛰고(skip) 2번째 문자부터 검사할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;패턴의 5번째 문자 'B'까지 일치하는 경우&lt;/b&gt;: 패턴 이동 후 'AB'를 건너뛰고(skip) 3번째 문자부터 검사할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JcKFo/btrHPJJSR9q/02I1wL5PNXCK7QUUOizSr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JcKFo/btrHPJJSR9q/02I1wL5PNXCK7QUUOizSr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JcKFo/btrHPJJSR9q/02I1wL5PNXCK7QUUOizSr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJcKFo%2FbtrHPJJSR9q%2F02I1wL5PNXCK7QUUOizSr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;388&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 'A'와 'B'는 다시 시작하는 값을 표에서 각각 1과 2로 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G4NjY/btrHPepQMmg/mC4IRuw6pRLDxetOPKkjE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G4NjY/btrHPepQMmg/mC4IRuw6pRLDxetOPKkjE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G4NjY/btrHPepQMmg/mC4IRuw6pRLDxetOPKkjE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG4NjY%2FbtrHPepQMmg%2FmC4IRuw6pRLDxetOPKkjE1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1424&quot; height=&quot;388&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 이어서 패턴을 오른쪽으로 2칸 밀면 문자가 일치하지 않는다. 그래서 패턴의 끝 문자 'D'는 다시 시작하는 값을 0으로 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로써 표가 완성되었고, 이렇게 작성한 표는 &lt;b&gt;건너뛰기 표(skip table)&lt;/b&gt;라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;KMP법으로 문자열 검색하기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;# KMP법으로 문자열 검색하기

def kmp_match(txt: str, pat: str) -&amp;gt; int:
    &quot;&quot;&quot;KMP법으로 문자열 검색&quot;&quot;&quot;
    pt = 1 # txt를 따라가는 커서
    pp = 0 # pat를 따라가는 커서
    skip = [0] * (len(pat) + 1) # 건너뛰기 표

    # 건너뛰기 표 만들기
    skip[pt] = 0
    while pt != len(pat):
        if pat[pt] == pat[pp]:
            pt += 1
            pp += 1
            skip[pt] = pp
        elif pp == 0:
            pt += 1
            skip[pt] = pp
        else:
            pp = skip[pp]

    # 문자열 검색하기
    pt = pp = 0
    while pt != len(txt) and pp != len(pat):
        if txt[pt] == pat[pp]:
            pt += 1
            pp += 1
        elif pp == 0:
            pt += 1
        else:
            pp = skip[pp]

    return pt - pp if pp == len(pat) else -1

if __name__ == &quot;__main__&quot;:
    s1 = input(&quot;텍스트를 입력하세요.: &quot;) # 텍스트용 문자열
    s2 = input(&quot;패턴을 입력하세요.: &quot;)  # 패턴용 문자열

    idx = kmp_match(s1, s2) # 문자열 s1 ~ s2까지를 KMP법으로 검색

    if idx == -1:
        print(&quot;텍스트 안에 패턴이 존재하지 않습니다.&quot;)
    else:
        print(f&quot;텍스트안에 문자열이 {idx + 1}번째 위치에 있습니다.&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과&lt;/p&gt;
&lt;pre id=&quot;code_1727529636372&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;텍스트를 입력하세요.: ABABCDEFGHA
패턴을 입력하세요.: ABC
텍스트안에 문자열이 3번째 위치에 있습니다.

Process finished with exit code 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KMP법으로 kmp_match( ) 함수가 전달받는 인수와 반환값은 브루트 포스법으로 bf_match( ) 함수가 전달받은 것과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드의 건너뛰기 표를 만드는 부분은 다시 시작하는 값을 건너뛰기 표로 만들고, 실질적으로 문자열을 검색하는 부분은 문자열 검색을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KMP법에서 텍스트를 스캔하는 커서 pt는 앞으로 나갈 뿐 뒤로 되돌아오지 않는다. 이것은 브루트 포스법에는 없는 특징이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 KMP 알고리즘은 복잡할 뿐, 보이어-무어법 보다 성능 면에서 같거나 오히려 낮은 수준이기때문에 실제 프로그램에서 별로 사용하지 않는다.&lt;/p&gt;</description>
      <category>자료구조, 알고리즘 입문/문자열 검색 알고리즘</category>
      <author>sungw00</author>
      <guid isPermaLink="true">https://vibeee.tistory.com/53</guid>
      <comments>https://vibeee.tistory.com/53#entry53comment</comments>
      <pubDate>Sat, 28 Sep 2024 22:25:02 +0900</pubDate>
    </item>
    <item>
      <title>[문자열 검색 알고리즘] 브루트 포스법</title>
      <link>https://vibeee.tistory.com/52</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;문자열 검색이란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 검색은 어떤 문자열 안에 다른 문자열이 포함되어 있는지 검사하고, 만약 포함되어 있다면 어디에 위치하는지 찾아내는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 문자열 &quot;STRING&quot;에서 &quot;IN&quot;을 검색하면 성공하지만, 문자열 &quot;QUEEN&quot;에서 &quot;IN&quot;을 검색하면 실패함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 검색에서 검색되는 쪽의 문자열을 텍스트(text), 찾아내는 문자열을 패턴(pattern)이라고 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;브루트 포스법이란?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 검색 알고리즘 중 가장 기초적이고 단순한 알고리즘. 선형 검색을 단순하게 확장한 알고리즘이라서 단순법이라고 불린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트 &quot;ABABCDEFGHA&quot;에서 패턴 &quot;ABC&quot;를 브루트 포스법으로 검색하는 순서는 다음과 같이 진행된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfCSdg/btrHAIw7Tz2/r27GsVY8BmFvUkKL7Pvar1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfCSdg/btrHAIw7Tz2/r27GsVY8BmFvUkKL7Pvar1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfCSdg/btrHAIw7Tz2/r27GsVY8BmFvUkKL7Pvar1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfCSdg%2FbtrHAIw7Tz2%2Fr27GsVY8BmFvUkKL7Pvar1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;956&quot; height=&quot;400&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 텍스트의 첫 문자 &quot;A&quot;에서 시작하는 문자 3개가 패턴 &quot;ABC&quot;와 일치하는지 검사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;A&quot;와 &quot;B&quot;는 일치하지만 마지막 &quot;C&quot;가 일치하지 않으므로 패스한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vyjYf/btrHwkX30j7/r5Fn8Z6CIq6hdcZipevaYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vyjYf/btrHwkX30j7/r5Fn8Z6CIq6hdcZipevaYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vyjYf/btrHwkX30j7/r5Fn8Z6CIq6hdcZipevaYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvyjYf%2FbtrHwkX30j7%2Fr5Fn8Z6CIq6hdcZipevaYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;958&quot; height=&quot;400&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 패턴을 오른쪽으로 1칸 밀고, 텍스트의 2번째 문자와 그 이후 부분이 일치하는지 검사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패턴의 첫 문자 &quot;A&quot;와 텍스트의 문자 &quot;B&quot;가 일치하지 않으므로 패스한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctqG0t/btrHDAkhlPj/70k0fmKhfqpkuzVMyVUBPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctqG0t/btrHDAkhlPj/70k0fmKhfqpkuzVMyVUBPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctqG0t/btrHDAkhlPj/70k0fmKhfqpkuzVMyVUBPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctqG0t%2FbtrHDAkhlPj%2F70k0fmKhfqpkuzVMyVUBPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;956&quot; height=&quot;400&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 패턴을 다시 오른쪽으로 1칸 밀고, 텍스트의 문자와 패턴의 문자가 일치하는지 검사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패턴의 문자 &quot;A&quot;, &quot;B&quot;, &quot;C&quot;와 텍스트의 문자 &quot;A&quot;, &quot;B&quot;, &quot;C&quot;가 모두 일치하므로 검색에 성공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 알고리즘을 상세히 나타내면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;1084&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LOWCD/btrHzpEyOY2/m084hKyiEZQRGhOLTqzeDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LOWCD/btrHzpEyOY2/m084hKyiEZQRGhOLTqzeDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LOWCD/btrHzpEyOY2/m084hKyiEZQRGhOLTqzeDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLOWCD%2FbtrHzpEyOY2%2Fm084hKyiEZQRGhOLTqzeDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1060&quot; height=&quot;1084&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;1084&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 텍스트와 패턴의 첫 문자를 위아래로 나란히 놓고 첫 문자부터 차례로 검사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과정 1과 과정 2처럼 문자가 일치하는 동안은 계속 검사하다가 과정 3처럼 일치하지 않는 다른 문자를 만나면 더 이상 검사가 불필요하다고 판단하고 다음 단계로 넘어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8IzBy/btrHxUY8Ink/EIRa1oR91qEErIXMkfsk21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8IzBy/btrHxUY8Ink/EIRa1oR91qEErIXMkfsk21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8IzBy/btrHxUY8Ink/EIRa1oR91qEErIXMkfsk21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8IzBy%2FbtrHxUY8Ink%2FEIRa1oR91qEErIXMkfsk21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1060&quot; height=&quot;338&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 과정 4의 패턴을 검사하는 시작 위치를 오른쪽으로 1칸 민다. 곧, 텍스트에서 인덱스가 1인 문자와 패턴에서 인덱스가 0인 문자를 위아래로 나란히 놓는다. 위 그림처럼 첫 문자부터 검사에 실패한 후 다음 단계로 넘어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;1084&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjMi1z/btrHwlvVj3r/ABKXT7UtfEpbetkqfxSHHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjMi1z/btrHwlvVj3r/ABKXT7UtfEpbetkqfxSHHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjMi1z/btrHwlvVj3r/ABKXT7UtfEpbetkqfxSHHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjMi1z%2FbtrHwlvVj3r%2FABKXT7UtfEpbetkqfxSHHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1058&quot; height=&quot;1084&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;1084&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 과정 5의 패턴을 검사하는 시작 위치를 오른쪽으로 1칸 민다. 곧, 텍스트에서 인덱스가 2인 문자와 패턴에서 인덱스가 0인 문자를 위아래로 나란히 놓는다. 패턴의 첫 문자부터 차례로 5, 6, 7의 과정처럼 검사하면 이번에는 모든 문자가 텍스트와 일치하여 검색에 성공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과정 3에서는 텍스트쪽의 검사 위치가 2번 인덱스까지 나아갔지만 과정 4에서는 1번 인덱스로 되돌아오는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 이미 검사한 위치를 기억하지 못하기 때문에 브루트 포스법은 효율이 좋지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;브루트 포스법 진행 순서&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;각 문자열을 따라가는 커서를 선언&lt;/li&gt;
&lt;li&gt;문자열[커서]가 0이 아닐때 동안 반복&lt;/li&gt;
&lt;li&gt;만약 문자열[커서] == 문자열[커서]라면, 각 커서 + 1&lt;/li&gt;
&lt;li&gt;아니라면 pt는 pt - pp + 1부터, pp는 0부터 진행&lt;/li&gt;
&lt;li&gt;결과&amp;nbsp;반환은&amp;nbsp;만약&amp;nbsp;pp가&amp;nbsp;pat의&amp;nbsp;끝까지&amp;nbsp;비교했다면(전부&amp;nbsp;다&amp;nbsp;일치해서&amp;nbsp;pp의&amp;nbsp;길이와&amp;nbsp;같아진&amp;nbsp;상태)&amp;nbsp;pt&amp;nbsp;-&amp;nbsp;pp를&amp;nbsp;반환, 아니면 -1 반환&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;브루트 포스법으로 문자열을 검색하는 코드&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4; text-align: start;&quot;&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;def bf_match(txt, pat):
    pt = 0  # txt를 따라가는 커서
    pp = 0  # pat를 따라가는 커서

    while pt != len(txt) and pp != len(pat):
        if txt[pt] == pat[pp]: 
            pt += 1
            pp += 1
        else:
            pt = pt - pp + 1
            pp = 0

    return pt - pp if pp == len(pat) else -1

if __name__ == &quot;__main__&quot;:
    s1 = input(&quot;텍스트를 입력하세요: &quot;)  # 텍스트용 문자열
    s2 = input(&quot;패턴을 입력하세요: &quot;)   # 패턴용 문자열

    idx = bf_match(s1, s2)  # 문자열 s1 ~ s2를 브루트 포스법으로 검색

    if idx == -1:
        print(&quot;텍스트 안에 패턴이 존재하지 않습니다.&quot;)
    else:
        print(f&quot;{(idx + 1)}번째 문자가 일치합니다.&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과&lt;/p&gt;
&lt;pre id=&quot;code_1727527279427&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;텍스트를 입력하세요: ABABCDEFGHA
패턴을 입력하세요: ABC
3번째 문자가 일치합니다.

Process finished with exit code 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 bf_match( ) 함수는 텍스트 txt에서 패턴 pat를 검색한다. 그러면 검색에 성공한 txt 위치의 인덱스를 반환한다. 텍스트 txt 안에 패턴 pat가 여러 번 포함된 경우에는 가장 앞쪽에 위치한 인덱스를 반환한다. 검색에 실패한 경우에는 -1을 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pt는 텍스트를 저장한 txt를 스캔하는 커서이다. 두 변수 모두 0으로 초기화하고 스캔을 하거나 패턴이 이동할 때마다 업데이트 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에도 멤버십 연산자인 in과 not in으로 어떤 문자열이 다른 문자열 안에 포함되어 있는지 검색할 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;find, index 계열 함수로 검색한 문자열의 위치를 반환하는 방법도 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;멤버십 연산자로 구현&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멤버십 연산자인 in과 not in을 사용하면 어떤 문자열이 다른 문자열 안에 포함되어 있는지 검색할 수 있다. 예를 들어 txt 안에 문자열 ptn이 포함되어 있는지 판단할 때에는 다음과 같이 수행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1658125380292&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ptn in txt		# ptn은 txt에 포함되어 있는가?
ptn not in txt		# ptn은 txt에 포함되어 있지 않는가?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법을 이용하여 어떤 문자열이 다른 문자열 안에 포함되어 있는지 판단할 수는 있지만 그 위치(인덱스)는 알지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;검색한 문자열의 위치를 알고 싶다면 위 코드와 같이 배열을 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;find, index 계열 함수로 구현&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;str 클래스형에 소속된 find( ), rfind( ), index( ), rindex( ) 함수는 문자열을 검색하여 검색한 문자열의 위치를 반환한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 4가지 함수를 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1658125230264&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;str.find(sub[, start [, end]])&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문자열 str의 [start:end]에 sub가 포함되면 그 가운데 가장 작은 인덱스를 반환하고, 그렇지 않으면 -1을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;find( ) 함수에서는 전달받은 인수 sub, start, end 중에서 end만 생략하거나 start와 end 둘 다 생략할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sub는 생략할 수 없다.(생략할 수 있는 인수 start와 end는 슬라이스 표기에 따라 지정한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이스 표기에 따라 start는 슬라이스의 시작 인덱스이며 기본값은 0이다. 또한 end는 슬라이스의 끝 인덱스이며 기본값은 슬라이스되는 시퀀스의 길이를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[ ]는 그 안의 인수를 생략할 수 있다는 것을 나타내는 표기이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1658125301573&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;str.rfind(sub[, start [, end]])&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문자열 str의 [start:end]에 sub가 포함되면 그 가운데 가장 큰 인덱스를 반환하고, 그렇지 않으면 -1을 반환한다. (생략할 수 있는 인수 start와 end는 슬라이스 표기에 따라 지정한다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1658125461010&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;str.index(sub[, start [, end]])&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문자열 str의 [start:end]에 sub가 포함되면 그 가운데 가장 작은 인덱스를 반환하고, 그렇지 않으면 예외 처리로 ValueError를 내보낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1658125526262&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;str.rindex(sub[, start [, end]])&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;rfind( ) 함수와 같은 기능을 수행한다. 다만 sub가 발견되지 않으면 예외 처리로 ValueError를 내보낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;with 계열 함수로 구현&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;with 계열 함수는 어떤 문자열이 다른 문자열의 시작이나 끝에 포함되어 있는지를 판단한다.&lt;/p&gt;
&lt;pre id=&quot;code_1658125583196&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;str.startswith(prefix[, start [, end]])&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문자열이 prefix로 시작하면 True, 그렇지 않으면 False를 반환한다. start가 지정되어 있으면 그 위치에서 판단을 시작하고, end가 지정되어 있으면 그 위치에서 비교를 중지한다.(start와 end는 생략 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1658125633921&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;str.endswith(suffix[, start [, end]])&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문자열이 suffix로 끝나면 True, 그렇지 않으면 False를 반환한다. start가 지정되어 있으면 그 위치에서 판단을 시작하고, end가 지정되어 있으면 그 위치에서 비교를 중지.(start와 end는 생략 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;startswith( ), endswith( ) 함수의 인수 start와 end는 생략할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>자료구조, 알고리즘 입문/문자열 검색 알고리즘</category>
      <author>sungw00</author>
      <guid isPermaLink="true">https://vibeee.tistory.com/52</guid>
      <comments>https://vibeee.tistory.com/52#entry52comment</comments>
      <pubDate>Sat, 28 Sep 2024 21:56:00 +0900</pubDate>
    </item>
    <item>
      <title>도수 정렬</title>
      <link>https://vibeee.tistory.com/292</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;도수 정렬은 원소의 대소 관계를 판단하지 않고 빠르게 정렬하는 알고리즘으로, 분포수 세기 정렬이라고도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;도수 정렬 알아보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 학습한 정렬 알고리즘에서는 두 원소의 키값을 비교하여 정렬했다. 하지만 도수 정렬은 원소를 비교할 필요가 없다는 특징이 있다. 아래 그림은 10점 만점 테스트에서 학생 9명의 점수를 도수 정렬하는 알고리즘을 나타낸 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;정렬할 배열은 a, 원소 수는 n, 점수의 최댓값은 max이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1단계: 도수 분포표 만들기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 아래 그림처럼 배열 a에 있는 학생들의 점수를 바탕으로 '각 점수에 해당하는 학생이 몇 명인가'를 나타내는 도수 분포표를 만들어야 한다. 도수 분포표를 저장하는 곳은 원소 수가 11개인 배열 f이다.(0~10점을 나타내기 위해 원소는 총 11개이다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 배열 f의 모든 원솟값을 0으로 초기화한다.(0) 그런 다음 배열 a의 맨 앞부터 스캔하면서 도수 분포표를 만든다. 처음에 주목한 a[0]은 5점이므로 f[5]에 1(1명 추가)을 증가시킨다.(1) 그 다음 a[1]은 7점이므로 f[7]에 1을 증가시킨다.(2) 이 작업을 배열 a의 맨 끝인 a[n - 1]까지 반복하면 배열 f의 도수 분포표가 완성된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1585&quot; data-origin-height=&quot;825&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cITWL8/btsJTCamTIK/msH9UG61dvlwyea6ExkaEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cITWL8/btsJTCamTIK/msH9UG61dvlwyea6ExkaEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cITWL8/btsJTCamTIK/msH9UG61dvlwyea6ExkaEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcITWL8%2FbtsJTCamTIK%2FmsH9UG61dvlwyea6ExkaEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;747&quot; height=&quot;389&quot; data-origin-width=&quot;1585&quot; data-origin-height=&quot;825&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2단계: 누적 도수 분포표 만들기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 '0점부터 n점까지 학생이 몇 명 있는지'를 누적된 값을 나타내는 누적 도수 분포표를 만든다. 아래 그림은 배열 f의 두 번째 원소부터 바로 앞의 원솟값을 더하는 과정을 나타낸다. 가장 아래에 있는 배열 f는 완성된 누적 도수 분포표이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;예를 들어 f[4]의 값인 6은 0~4점을 받은 학생의 누계가 6명이고, f[10]의 값인 9는 0~10점을 받은 학생의 누계가 9명이라는 것을 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1307&quot; data-origin-height=&quot;1441&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7Qm97/btsJUvH7gje/vyHAmQwUijiiJ34XjyaVr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7Qm97/btsJUvH7gje/vyHAmQwUijiiJ34XjyaVr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7Qm97/btsJUvH7gje/vyHAmQwUijiiJ34XjyaVr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7Qm97%2FbtsJUvH7gje%2FvyHAmQwUijiiJ34XjyaVr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;594&quot; height=&quot;655&quot; data-origin-width=&quot;1307&quot; data-origin-height=&quot;1441&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3단계: 작업용 배열 만들기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞의 단계에서 각 점수를 받은 학생이 몇 번째에 위치하는지 알 수 있으므로 이 시점에서 정렬은 거의 마쳤다고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남은 작업은 배열 a의 각 원솟값과 누적 도수 분포표 f를 대조하여 정렬을 완료한 배열을 만드는 것이다. 이 작업에는 배열 a와 원소 수가 같은 작업용 배열 b가 필요하다. 배열 a의 원소를 맨 끝에서 맨 앞으로 스캔하면서 배열 f와 대조한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림은 작업용 배열을 만드는 과정을 보여준다. 이 그림과 작업용 배열 b를 만드는 아래의 for 문을 같이 비교하면서 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1601&quot; data-origin-height=&quot;483&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8CYcP/btsJTSRrLhh/f7x0dUU2N38deTRqp3zawk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8CYcP/btsJTSRrLhh/f7x0dUU2N38deTRqp3zawk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8CYcP/btsJTSRrLhh/f7x0dUU2N38deTRqp3zawk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8CYcP%2FbtsJTSRrLhh%2Ff7x0dUU2N38deTRqp3zawk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;211&quot; data-origin-width=&quot;1601&quot; data-origin-height=&quot;483&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림에서 배열 a의 맨 끝 원소인 a[8]의 값은 3이다. 누적 도수를 나타내는 배열 f[3]의 값이 5이므로 0~3점 사이에 학생이 5명 있다는 의미이다. 그러므로 작업용 배열 b[4]에 3을 저장한다.(2) 저장을 하기 전에 f[a[i]] -= 1을 수행한다.(1) 즉, f[3]의 값을 5에서 4로 1만큼 감소시켜 만든다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;배열 b의 다섯 번째 원소 인덱스는 5가 아닌 4라는 점을 주의해야 한다. 배열 b에 저장하기 전 (1)에 의해 f[a[i]]를 1 감소시킨다. 즉, f[3]의 값이 5에서 4가 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;247&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdjjSJ/btsJTp93DTF/gZcgmU4qUAiWGLJvSIGnuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdjjSJ/btsJTp93DTF/gZcgmU4qUAiWGLJvSIGnuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdjjSJ/btsJTp93DTF/gZcgmU4qUAiWGLJvSIGnuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdjjSJ%2FbtsJTp93DTF%2FgZcgmU4qUAiWGLJvSIGnuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;302&quot; height=&quot;147&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;247&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림에서 배열 a는 맨 끝에서 맨 앞을 향해 스캔하므로 a[8]의 하나 앞 원소인 a[7]의 값 1에 주목한다. 누적 도수를 나타내는 배열 f[1]의 값 2는 0~1점 사이에 학생이 2명 있다는 것을 보여준다. 그러므로 작업용 배열 b[1]에 1을 저장한다.(위 그림의 2번 코드)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;배열 b의 두 번째 원소 인덱스는 1이다. 배열 b에 저장하기 전에 f[1]의 값을 2에서 1로 1 만큼 감소시킨다.(위 그림의 1번 코드)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1542&quot; data-origin-height=&quot;483&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TH9a6/btsJViH4FOw/G9Dlt1Zeqryw6it01MrFm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TH9a6/btsJViH4FOw/G9Dlt1Zeqryw6it01MrFm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TH9a6/btsJViH4FOw/G9Dlt1Zeqryw6it01MrFm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTH9a6%2FbtsJViH4FOw%2FG9Dlt1Zeqryw6it01MrFm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;678&quot; height=&quot;212&quot; data-origin-width=&quot;1542&quot; data-origin-height=&quot;483&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림에서 다음에 주목하는 a[6]의 값은 3이다. 3점인 학생은 이전 그림에서처럼 이미 저장했으므로 두 번째 하는 것이다. 이전 그림에서는 a[8]을 작업용 배열 b에 저장할 때 f[3]의 값을 1 감소시켜 5에서 4로 만들었다. 작업용 배열의 네 번째 원소인 b[3]에 저장한다. 이렇게 미리 값을 감소시켰기 때문에 중복되는 값을 3인 배열 b[3]에 저장할 수 있다. 작업용 배열 b에 값을 저장할 때 참조한 배열 f의 원솟값을 1 감소시킨 이유는 같은 값의 원소를 중복으로 처리하지 않기 위한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬하기 전 배열의 맨 끝 쪽 a[8]의 값인 3은 b[4]에 저장되고, 맨 앞 쪽 a[6]의 값인 3은 b[3]에 저장되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업을 a[0]까지 수행하면 배열 a의 모든 원소가 작업용 배열 b의 알맞은 위치에 저장되고 이로써 모든 정렬이 완료된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1542&quot; data-origin-height=&quot;483&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UiL5v/btsJUjuq8Sj/7yW2vTmlz3o92oJy7l4IhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UiL5v/btsJUjuq8Sj/7yW2vTmlz3o92oJy7l4IhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UiL5v/btsJUjuq8Sj/7yW2vTmlz3o92oJy7l4IhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUiL5v%2FbtsJUjuq8Sj%2F7yW2vTmlz3o92oJy7l4IhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;649&quot; height=&quot;203&quot; data-origin-width=&quot;1542&quot; data-origin-height=&quot;483&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4단계: 배열 복사하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬은 완료되었지만 정렬한 결과가 저장되는 것은 작업용 배열 b이므로 배열 a는 정렬하기 전의 상태이다. 그러므로 아래의 for 문을 수행하여 배열 b의 모든 원소를 배열 a에 그대로 복사한다.&lt;/p&gt;
&lt;pre id=&quot;code_1727189069589&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for i in range(n):
    a[i] = b[i]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도수 정렬은 if 문을 사용하지 않고 for 문만 반복해서 정렬할 수 있는 알고리즘이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 프로그램은 이러한 도수 정렬을 수행하는 프로그램이다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4; text-align: start;&quot;&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;# 도수 정렬 알고리즘 구현하기

from typing import MutableSequence

def fsort(a: MutableSequence, max: int) -&amp;gt; None:
    &quot;&quot;&quot;도수 정렬(배열 원솟값은 0 이상 max 이하)&quot;&quot;&quot;
    n = len(a)                  # 정렬할 배열 a
    f = [0] * (max + 1)         # 누적 도수 분포표 배열 f
    b = [0] * n                 # 작업용 배열 b
    
    for i in range(n):               f[a[i]] += 1                       # [1단계]
    for i in range(1, max + 1):      f[i] += f[i - 1]                   # [2단계]
    for i in range(n - 1, -1, -1):   f[a[i]] -= 1; b[f[a[i]]] = a[i]    # [3단계]
    for i in range(n):               a[i] = b[i]                        # [4단계]
    
def counting_sort(a: MutableSequence) -&amp;gt; None:
    &quot;&quot;&quot;도수 정렬&quot;&quot;&quot;
    fsort(a, max(a))
    
if __name__ == '__main__':
    print('도수 정렬을 수행합니다.')
    num = int(input('원소 수를 입력하세요.: '))
    x = [None] * num            # 원소 수가 num인 배열을 생성
    
    for i in range(num):        # 양수만 입력받도록 제한
        while True:
            x[i] = int(input(f'x[{i}]: '))
            if x[i] &amp;gt;= 0: break
            
    counting_sort(x)            # 배열 x를 도수 정렬
    
    print('오름차순으로 정렬했습니다.')
    for i in range(num):
        print(f'x[{i}]: {x[i]}')&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과&lt;/p&gt;
&lt;pre id=&quot;code_1727189511891&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;도수 정렬을 수행합니다.
원소 수를 입력하세요.: 7
x[0]: 22
x[1]: 5
x[2]: 11
x[3]: 32
x[4]: 99
x[5]: 68
x[6]: 70
오름차순으로 정렬했습니다.
x[0]: 5
x[1]: 11
x[2]: 22
x[3]: 32
x[4]: 68
x[5]: 70
x[6]: 99

Process finished with exit code 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fsort( ) 함수는 도수 정렬을 수행한다. 배열의 모든 원솟값이 0 이상 max 이하라는 것을 전제로 해서 배열 a를 정렬한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;counting_sort( ) 함수는 배열 a와 그 원소의 최댓값 max(a)를 fsort( ) 함수에 전달하여 호출한다. 실행 결과를 보면 원소의 최댓값이 99이므로 호출식 fsort(a, max(a))는 fsort(a, 99)가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;메인 함수 내 for 문에서는 입력 받는 값을 0 이상의 값(양수)으로 제한한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;08~09행: fsort( ) 함수 안의 앞부분에서는 두 배열 f와 b를 생성한다. 앞에서 살펴본 것과 같이 배열 f는 도수 분포와 누적 도수를 저장하는 배열이고, 배열 b는 정렬한 배열을 임시로 저장하는 작업용 배열이다. 두 배열의 전체 원솟값을 0으로 초기화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열 f는 0~max인 원소가 필요하므로 원소 수는 max + 1이다. 또 배열 b는 정렬 결과를 임시로 저장하는 배열이므로 원소 수는 배열 a와 같은 n이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11~14행: fsort( ) 함수는 4단계로 구성된다. 1~4단계까지 각 단계별 내용은 지금까지 학습한 도수 정렬의 단계와 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도수 정렬 알고리즘은 데이터 비교&lt;b&gt;&amp;middot;&lt;/b&gt;교환 작업이 필요 없어 매우 빠르다. 프로그램에서는 단일 for 문만 사용하고 재귀 호출이나 이중 if 문이 없어 매우 효율이 좋은 알고리즘이다. 하지만 도수분포표가 필요하므로(예를 들어 0, 1, ..., 100점인 시험 점수와 같이) 데이터의 최솟값과 최댓값을 미리 알고 있는 경우에만 적용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fsort( ) 함수는 배열 a의 원솟값이 0 이상 max 이하인 것을 전제로 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 단계(for 문)에서 배열 원소를 건너뛰지 않고 순서대로 스캔하므로 이 정렬 알고리즘은 안정적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 3단계에서 배열 a를 스캔할 때 맨 앞부터 스캔하면 안정적이지 않다는 점을 주의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;맨 앞에서 맨 끝을 향해 스캔하면 안정적이지 않은 이유는 같은 키값의 순서 관계가 정렬 전후로 뒤바뀌기 때문이다.&lt;/p&gt;</description>
      <author>sungw00</author>
      <guid isPermaLink="true">https://vibeee.tistory.com/292</guid>
      <comments>https://vibeee.tistory.com/292#entry292comment</comments>
      <pubDate>Tue, 24 Sep 2024 23:59:14 +0900</pubDate>
    </item>
    <item>
      <title>[정렬 알고리즘] 병합 정렬(합병 정렬, 머지 소트, 머지 정렬)</title>
      <link>https://vibeee.tistory.com/49</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;병합 정렬이란?&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;- 배열을 앞부분과 뒷부분의 두 그룹으로 나누어 각각 정렬한 후 병합하는 작업을 반복(재귀)&lt;br /&gt;- 퀵 정렬과 다르게 피벗값이 존재하지 않음(배열 항상 반으로 쪼개기 때문)&lt;br /&gt;- 병합할 때 2의 배수만큼 병합함&lt;br /&gt;- 시간 복잡도는 O(n log n) 을 보장&lt;br /&gt;- 작업용 배열을 생성할 때 추가적인 데이터 공간이 필요하므로 메모리 활용면에서는 비효율적이라는 문제가 있음&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;정렬을 마친 두 배열의 병합 과정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;902&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0a5Ui/btrGEg3aN7A/DUwkF7KoVEKHcebYOKp7X0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0a5Ui/btrGEg3aN7A/DUwkF7KoVEKHcebYOKp7X0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0a5Ui/btrGEg3aN7A/DUwkF7KoVEKHcebYOKp7X0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0a5Ui%2FbtrGEg3aN7A%2FDUwkF7KoVEKHcebYOKp7X0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1730&quot; height=&quot;902&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;902&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병합을 마치면 위와 같이 정렬이 완료되는데 그 과정을 자세히 들여다 보면 세 가지 과정으로 수행된다.&lt;br /&gt;1) 각 배열의 커서인 pa, pb, pc를 만들고 각각 0을 저장, 각 배열의 원소 수를 담는 na, nb, nc를 만들고 원소의 길이를 저장.&lt;br /&gt;2) pa와 pb를 비교하여 작은 값을 pc에 저장. pa가 작다면 pc에 pa를 담아주고 pa와 pc를 한 칸씩 이동, pb가 작다면 pc에 pb를 담아주고 pb와 pc를 한 칸 이동.&lt;br /&gt;3) 배열 a 또는 배열 b에 남은 원소를 배열 c에 복사. 위 예시에서는 배열 a의 길이가 1만큼 더 길기때문에 복사 후 pa와 pc를 한 칸 이동.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;정렬을 마친 두 배열을 병합하는 코드&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 과정을 코드로 옮기면 다음과 같이 나타낼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 정렬을 마친 두 배열을 병합하기

from typing import Sequence, MutableSequence

def merge_sorted_list(a: Sequence, b: Sequence, c: MutableSequence) -&amp;gt; None:
    &quot;&quot;&quot;정렬을 마친 배열 a와 b를 병합하여 c에 저장&quot;&quot;&quot;
    pa, pb, pc = 0, 0, 0 # 각 배열의 커서
    na, nb, nc = len(a), len(b), len(c) # 각 배열의 원소 수

    while pa &amp;lt; na and pb &amp;lt; nb: # pa와 pb를 비교하여 작은 값을 pc에 저장
        if a[pa] &amp;lt;= b[pb]:
            c[pc] = a[pa]
            pa += 1
        else:
            c[pc] = b[pb]
            pb += 1
        pc += 1

    while pa &amp;lt; na: # a에 남은 원소를 c에 복사
        c[pc] = a[pa]
        pa += 1
        pc += 1

    while pb &amp;lt; nb: # b에 남은 원소를 c에 복사
        c[pc] = b[pb]
        pb += 1
        pc += 1

if __name__ == &quot;__main__&quot;:
    a = [2, 4, 6, 8, 11, 13]
    b = [1, 2, 3, 4, 9, 16, 21]
    c = [None] * (len(a) + len(b))
    print(&quot;정렬을 마친 두 배열의 병합을 수행합니다.&quot;)

    merge_sorted_list(a, b, c)  # 배열 a와 b를 병합하여 c에 저장

    print('배열 a와 b를 병합하여 배열 c에 저장했습니다.')
    print(f'배열 a: {a}')
    print(f'배열 b: {b}')
    print(f'배열 c: {c}')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10~17행: 첫 번째 while문 - 배열 a의 a[pa]와 배열 b의 b[pb]를 주목하여 이 가운데 작은 값을 c[pc]에 저장한다. 이어서 a, b, c 배열의 커서를 1씩 증가시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림에서는 a[0]과 b[0]을 비교하여 작은 값 1을 c[0]에 대입한다. 이후 b와 c 배열의 커서 pb와 pc만 1칸씩 오른쪽으로 이동한다. 이때 값을 꺼내지 않은 배열 a의 커서 pa는 이동하지 않는다. 이처럼 a[pa]와 b[pb]를 비교하여 작은 값을 c[pc]에 대입하고, 꺼낸 쪽 배열의 커서와 배열 c의 커서를 이동하는 작업을 반복한다. 커서 pa와 pb가 각각 배열의 맨 끝에 도달하면 while 문이 종료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;19~22행: 두 번째 while문 - 이 while문은 앞에서 배열 b의 모든 원소를 배열 c로 복사했지만, 배열 a에 아직 복사하지 않은 원소가 있으면 실행된다. 즉, 커서 pa가 배열 a의 맨 끝에 도달하지 않은 경우이다. 커서를 이동시키면서 배열 a에 남은 원소를 배열 c에 복사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;24~27행: 세 번째 while문 - 이 while문은 앞에서 배열 a의 모든 원소를 배열 c로 복사했지만, 배열 b에 아직 복사하지 않은 원소가 있으면 실행된다. 즉, 커서 pb가 배열 b의 맨 끝에 도달하지 않은 경우이다. 커서를 이동시키면서 배열 b에 남은 모든 원소를 배열 c에 복사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⭐️ 파이썬에서 제공하는 &lt;b&gt;sorted( )&lt;/b&gt; 함수를 사용하여 다음과 같이 병합할 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;c = list(sorted(a + b))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 위 방법은 속도가 느리다는 단점이 있으므로, 아래와 같이 &lt;b&gt;heapq 모듈의 merge( )&lt;/b&gt; 함수를 사용하면 빠르게 병합할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 정렬을 마친 두 배열의 병합(heapq.merge 사용)

import heapq

a = [1, 2, 4, 5, 7]
b = [3, 6, 8, 9]
c = list(heapq.merge(a, b)) # 배열 a와 b를 병합하여 배열c에 저장
(... 생략 ...)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;병합 정렬의 진행 과정&lt;br /&gt;병합 정렬은 분할 정복법에 따라 배열을 앞부분과 뒷부분으로 나누어 정렬하는 것이 기본이다. &lt;br /&gt;1) 우선 배열을 아래와 같이 나눈다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;952&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0cDWt/btrGEJ5pgLP/H40wUqgz3kLMKphxs1hBc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0cDWt/btrGEJ5pgLP/H40wUqgz3kLMKphxs1hBc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0cDWt/btrGEJ5pgLP/H40wUqgz3kLMKphxs1hBc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0cDWt%2FbtrGEJ5pgLP%2FH40wUqgz3kLMKphxs1hBc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1794&quot; height=&quot;952&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;952&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 결국 1개의 배열 방에 1개의 원소만이 남게되고, 이 원소들을 다시 정렬하여 배열로 만들어주면 위의 그림과 같아진다.&lt;br /&gt;&lt;br /&gt;이 배열들을 전부 정렬하고 병합하여 다시 원래의 배열에 옮겨 담아주는 순서가 아래 그림이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1792&quot; data-origin-height=&quot;1296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1T32h/btrGEJqNSAd/kKVJaviJGW7HhasA2ckmu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1T32h/btrGEJqNSAd/kKVJaviJGW7HhasA2ckmu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1T32h/btrGEJqNSAd/kKVJaviJGW7HhasA2ckmu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1T32h%2FbtrGEJqNSAd%2FkKVJaviJGW7HhasA2ckmu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1792&quot; height=&quot;1296&quot; data-origin-width=&quot;1792&quot; data-origin-height=&quot;1296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬하고 병합하는 과정을 마치게 되면 오름차순으로 병합정렬이 완료되는 것을 확인할 수 있다.&lt;br /&gt;위 과정을 코드로 나타내면 다음과 같다.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;병합 정렬 과정을 표현한 코드&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;# 병합 정렬 알고리즘 구현하기

from typing import MutableSequence

def merge_sort(a: MutableSequence) -&amp;gt; None:
      &quot;&quot;&quot;병합 정렬&quot;&quot;&quot;

      def _merge_sort(a: MutableSequence, left: int, right: int) -&amp;gt; None:
            &quot;&quot;&quot;a[left] ~ a[right]를 재귀적으로 병합 정렬&quot;&quot;&quot;
            if left &amp;lt; right:
                  center = (left + right) // 2

                  _merge_sort(a, left, center)  # 배열 앞부분을 병합 정렬
                  _merge_sort(a, center + 1, right)   # 배열 뒷부분을 병합 정렬

                  p = j = 0
                  i = k = left

                  while i &amp;lt;= center:      # 배열 앞부분 복사
                        buff[p] = a[i]
                        p += 1
                        i += 1

                  while i &amp;lt;= right and j &amp;lt; p:
                        if buff[j] &amp;lt;= a[i]:
                              a[k] = buff[j]
                              j += 1
                        else:
                              a[k] = a[i]
                              i += 1
                        k += 1

                  while j &amp;lt; p:
                        a[k] = buff[j]
                        k += 1
                        j += 1

      n = len(a)
      buff = [None] * n                   # 작업용 배열을 생성
      _merge_sort(a, 0, n - 1)       # 배열 전체를 병합 정렬
      del buff                            # 작업용 배열을 소멸

if __name__ == '__main__':
      print('병합 정렬을 수행합니다.')
      num = int(input('원소 수를 입력하세요.: '))
      x = [None] * num                    # 원소 수가 num인 배열을 생성

      for i in range(num):
            x[i] = int(input(f'x[{i}]: '))

      merge_sort(x)                       # 배열 x를 병합 정렬

      print('오름차순으로 정렬했습니다.')
      for i in range(num):
            print(f'x[{i}] = {x[i]}')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과&lt;/p&gt;
&lt;pre id=&quot;code_1727184746847&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;병합 정렬을 수행합니다.
원소 수를 입력하세요.: 9
x[0]: 5
x[1]: 8
x[2]: 4
x[3]: 2
x[4]: 6
x[5]: 1
x[6]: 3
x[7]: 9
x[8]: 7
오름차순으로 정렬했습니다.
x[0] = 1
x[1] = 2
x[2] = 3
x[3] = 4
x[4] = 5
x[5] = 6
x[6] = 7
x[7] = 8
x[8] = 9

Process finished with exit code 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 통해 a[left] ~ a[right]를 재귀적으로 정렬하여 최종적으로 병합 정렬 된 배열을 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;⭐️ 배열 병합의 시간 복잡도는 O(n)이고, 데이터 원소 수가 n일 때 병합 정렬의 단계는 log n만큼 필요하므로 전체 시간 복잡도는 O(n log n)이다. 또한, 병합 정렬 알고리즘은 서로 떨어져 있는 원소를 교환하지 않으므로 안정적인 알고리즘이다.&lt;/p&gt;</description>
      <category>자료구조, 알고리즘 입문/정렬 알고리즘</category>
      <author>sungw00</author>
      <guid isPermaLink="true">https://vibeee.tistory.com/49</guid>
      <comments>https://vibeee.tistory.com/49#entry49comment</comments>
      <pubDate>Tue, 24 Sep 2024 22:33:03 +0900</pubDate>
    </item>
    <item>
      <title>[정렬 알고리즘] 퀵 정렬 - 피벗을 선택하는 두 가지 방법</title>
      <link>https://vibeee.tistory.com/48</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 글에서 퀵 정렬에 대해 살펴보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://vibeee.tistory.com/47&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://vibeee.tistory.com/47&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1657003175944&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[정렬 알고리즘] 퀵 정렬&quot; data-og-description=&quot;퀵 정렬이란? - 일반적으로 사용되는 아주 빠른(quick) 정렬 알고리즘 - Charles A. R Hoare에 의해 고안됨 - 중심 축이 되는 값(피벗)을 선택하여 그룹을 나누어가는 방법 - 재귀적으로 호출하여 진행하&quot; data-og-host=&quot;vibeee.tistory.com&quot; data-og-source-url=&quot;https://vibeee.tistory.com/47&quot; data-og-url=&quot;https://vibeee.tistory.com/47&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b0pmMk/hyOZHb2Q04/F4dwr9DaHzqQmCSLkX0G40/img.png?width=800&amp;amp;height=640&amp;amp;face=0_0_800_640,https://scrap.kakaocdn.net/dn/btDzui/hyOZNi3Wux/KixVRW5nHGm8Kr9qU3hd11/img.png?width=800&amp;amp;height=640&amp;amp;face=0_0_800_640,https://scrap.kakaocdn.net/dn/bbsgSJ/hyOZDtVY6b/s1zz6T62WYNYmErMkILVi0/img.png?width=1410&amp;amp;height=1128&amp;amp;face=0_0_1410_1128&quot;&gt;&lt;a href=&quot;https://vibeee.tistory.com/47&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://vibeee.tistory.com/47&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b0pmMk/hyOZHb2Q04/F4dwr9DaHzqQmCSLkX0G40/img.png?width=800&amp;amp;height=640&amp;amp;face=0_0_800_640,https://scrap.kakaocdn.net/dn/btDzui/hyOZNi3Wux/KixVRW5nHGm8Kr9qU3hd11/img.png?width=800&amp;amp;height=640&amp;amp;face=0_0_800_640,https://scrap.kakaocdn.net/dn/bbsgSJ/hyOZDtVY6b/s1zz6T62WYNYmErMkILVi0/img.png?width=1410&amp;amp;height=1128&amp;amp;face=0_0_1410_1128');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[정렬 알고리즘] 퀵 정렬&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;퀵 정렬이란? - 일반적으로 사용되는 아주 빠른(quick) 정렬 알고리즘 - Charles A. R Hoare에 의해 고안됨 - 중심 축이 되는 값(피벗)을 선택하여 그룹을 나누어가는 방법 - 재귀적으로 호출하여 진행하&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;vibeee.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;퀵 정렬에서의 피벗&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀵 정렬의 경우 피벗을 선택하는 방법에 따라 성능이 달라지기 때문에 결국 피벗을 선택하는 방법이 성능을 좌우한다고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시를 통해 피벗을 선택하는 예시를 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-07-05 오후 3.41.34.png&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNFOAf/btrGzDQaFuv/6ZKb9MBtwMEttLMzwTMUk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNFOAf/btrGzDQaFuv/6ZKb9MBtwMEttLMzwTMUk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNFOAf/btrGzDQaFuv/6ZKb9MBtwMEttLMzwTMUk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNFOAf%2FbtrGzDQaFuv%2F6ZKb9MBtwMEttLMzwTMUk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;830&quot; height=&quot;138&quot; data-filename=&quot;스크린샷 2022-07-05 오후 3.41.34.png&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 위와 같은 배열이 있을 때, 맨 앞 원소(8)를 선택한다고 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 8이 피벗으로 선택된다면, 0~7과 8이 있는 두 가지 그룹으로 나눌 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 나누어지게 된다면 한쪽으로 완전히 치우쳐진 분할을 반복하기 때문에 빠른 정렬 속도를 기대할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 하나 예를 들어보면 배열을 정렬한 뒤 가운데에 위치하는 값, 즉 전체에서 중앙값을 피벗으로 선택하게 된다면 어떨까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열이 한쪽으로 치우치지 않고 절반 크기로 나누어져 이상적인 정렬의 효과를 기대할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 정렬된 배열에서 중앙값을 구하려면 그에 대한 처리가 필요하고, 많은 계산 시간을 소모하기 때문에 결국 피벗을 선택하는 의미가 없어진다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;피벗을 선택하는 방법 2가지&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;방법 1)&lt;/b&gt; 나누어야 할 배열의 원소 수가 3 이상이면, 배열에서 임의의 원소 3개를 꺼내 중앙값인 원소를 피벗으로 선택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 맨 앞 원소(8), 가운데 원소(4), 맨 끝 원소(0) 중에서 중앙값인 4를 피벗으로 선택하면 치우치지 않고 나눌 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-07-05 오후 3.53.17.png&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ecTqA4/btrGzdRVR5d/2ad1AfDsbYk3eFXhMNCl2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ecTqA4/btrGzdRVR5d/2ad1AfDsbYk3eFXhMNCl2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ecTqA4/btrGzdRVR5d/2ad1AfDsbYk3eFXhMNCl2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FecTqA4%2FbtrGzdRVR5d%2F2ad1AfDsbYk3eFXhMNCl2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;838&quot; height=&quot;182&quot; data-filename=&quot;스크린샷 2022-07-05 오후 3.53.17.png&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;182&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;방법 2-1)&lt;/b&gt; 나누어야 할 배열의 맨 앞 원소, 가운데 원소, 맨 끝 원소를 정렬한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1056&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Br52K/btrGuTN9maO/gfPfwvfNKz0tW8Kdc4ZGuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Br52K/btrGuTN9maO/gfPfwvfNKz0tW8Kdc4ZGuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Br52K/btrGuTN9maO/gfPfwvfNKz0tW8Kdc4ZGuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBr52K%2FbtrGuTN9maO%2FgfPfwvfNKz0tW8Kdc4ZGuk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1056&quot; height=&quot;498&quot; data-origin-width=&quot;1056&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;방법 2-2)&lt;/b&gt;&lt;span&gt;&lt;span&gt; 가운데 원소와 맨 끝에서 두 번째 원소를 교환한 후&amp;nbsp;맨 끝에서 두 번째 원솟값 a[right - 1]이 피벗으로 선택한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;770&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MFwB0/btrGyMAiaEK/QNAaK36KWSn9E6LofskJHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MFwB0/btrGyMAiaEK/QNAaK36KWSn9E6LofskJHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MFwB0/btrGyMAiaEK/QNAaK36KWSn9E6LofskJHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMFwB0%2FbtrGyMAiaEK%2FQNAaK36KWSn9E6LofskJHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1072&quot; height=&quot;770&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;770&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;방법 2-3)&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;그룹으로&amp;nbsp;&lt;/span&gt;&lt;/span&gt;나눌 대상을 a[left + 1] ~ a[right - 2]로 좁힌다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5QRRX/btrGw2XWQeV/3tNgkY2HWokL0GHvbd3tKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5QRRX/btrGw2XWQeV/3tNgkY2HWokL0GHvbd3tKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5QRRX/btrGw2XWQeV/3tNgkY2HWokL0GHvbd3tKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5QRRX%2FbtrGw2XWQeV%2F3tNgkY2HWokL0GHvbd3tKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1072&quot; height=&quot;338&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 거치고 나면 스캔할 커서의 시작 위치(pl, pr)를 다음과 같이 변경하여 나눌 대상 범위를 좁힐 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;왼쪽 커서 pl의 시작 위치: left&amp;nbsp;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;rarr; left + 1&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;오른쪽 커서 pr의 시작 위치: right&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;rarr; right - 2&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px; color: #006dd7;&quot;&gt;&lt;b&gt;방법 2를 적용하여 구현한 코드&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1657005953892&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def select_value(arr, idx1, idx2, idx3):
    if arr[idx2] &amp;lt; arr[idx1]: arr[idx2], arr[idx1] = arr[idx1], arr[idx2]
    if arr[idx3] &amp;lt; arr[idx2]: arr[idx3], arr[idx2] = arr[idx2], arr[idx3]
    if arr[idx2] &amp;lt; arr[idx1]: arr[idx2], arr[idx1] = arr[idx1], arr[idx2]
    return idx2

def qsort(arr, left, right):
    pl = left # 왼쪽 포인터 pl
    pr = right # 오른쪽 포인터 pr
    middle = select_value(arr, pl, (pl + pr) // 2, pr) # 중앙값 인덱스 반환
    pivot = arr[middle]  # 피벗 pivot

    arr[middle], arr[pr - 1] = arr[pr - 1], arr[middle] # 중앙값과 pr - 1 값 교환
    pl += 1 # pl의 시작 위치 변경
    pr -= 2 # pr의 시작 위치 변경

    while pl &amp;lt;= pr:
        while arr[pl] &amp;lt; pivot: pl += 1
        while arr[pr] &amp;gt; pivot: pr -= 1
        if pl &amp;lt;= pr:
            arr[pl], arr[pr] = arr[pr], arr[pl]
            pl += 1
            pr -= 1

    if left &amp;lt; pr: qsort(arr, left, pr) # pr이 a[0]보다 오른쪽에 위치하면 왼쪽 그룹 나누기
    if pl &amp;lt; right: qsort(arr, pl, right) # pl이 a[8]보다 왼쪽에 위치하면 오른쪽 그룹 나누기

def quick_sort(arr):
    qsort(arr, 0, len(arr) - 1)

print(&quot;퀵 정렬을 수행합니다.&quot;)
num = int(input(&quot;원소 수를 입력하세요.: &quot;))
array = [None] * num

for i in range(num):
    array[i] = int(input(f&quot;array[{i}]: &quot;))

quick_sort(array)

print(&quot;오름차순으로 정렬했습니다.&quot;)

for i in range(num):
    print(f&quot;array[{i}] = {array[i]}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;letter-spacing: 0px; background-color: #006dd7; color: #ffffff;&quot;&gt;⭐️ 위 방법을 사용하면 나누는 그룹이 한쪽으로 치우치는 것을 방지할 수 있고, 스캔할 원소를 3개 줄일 수 있으므로, 이 방법을 사용하지 않을 때보다 조금 더 빠른 속도로 정렬할 수 있다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;퀵 정렬의 시간 복잡도와 삽입 정렬로 전환하기&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;퀵 정렬의 일반적으로 알려져있는 시간 복잡도는 &lt;b&gt;O(n log n)&lt;/b&gt;이다. 하지만 위와 같이 정렬하는 배열의 초깃값이나 피벗을 선택하는 방법에 따라서 실행시간 복잡도가 증가하는 경우도 있다. 예를 들어 매번 1개의 원소와 나머지 원소로 나누어진다면 n번의 분할이 필요해지기 때문에 &lt;b&gt;최악의 시간복잡도&lt;/b&gt;인 &lt;b&gt;O(n^2)&lt;/b&gt;이 된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 원소 수가 적을때는 아래 코드와 같이 퀵 정렬이 아닌 삽입 정렬로 전환하여 실행하면 효율적으로 정렬을 수행할 수가 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1657006451350&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def select_value(arr, idx1, idx2, idx3):
    if arr[idx2] &amp;lt; arr[idx1]: arr[idx2], arr[idx1] = arr[idx1], arr[idx2]
    if arr[idx3] &amp;lt; arr[idx2]: arr[idx3], arr[idx2] = arr[idx2], arr[idx3]
    if arr[idx2] &amp;lt; arr[idx1]: arr[idx2], arr[idx1] = arr[idx1], arr[idx2]
    return idx2

def insertion_sort(arr, left, right):
    for i in range(left + 1, right + 1):
        j = i
        tmp = arr[i]
        while j &amp;gt; 0 and arr[j - 1] &amp;gt; tmp:
            arr[j] = arr[j - 1]
            j -= 1
        arr[j] = tmp

def qsort(arr, left, right):
    if right - left &amp;lt; 9: # 원소 수가 9 미만이면 단순 삽입 정렬로 전환
        insertion_sort(arr, left, right)
    else:
        pl = left # 왼쪽 포인터 pl
        pr = right # 오른쪽 포인터 pr
        middle = select_value(arr, pl, (pl + pr) // 2, pr) # 중앙값 인덱스 반환
        pivot = arr[middle]  # 피벗 pivot
    
        arr[middle], arr[pr - 1] = arr[pr - 1], arr[middle] # 중앙값과 pr - 1 값 교환
        pl += 1 # pl의 시작 위치 변경
        pr -= 2 # pr의 시작 위치 변경
    
        while pl &amp;lt;= pr:
            while arr[pl] &amp;lt; pivot: pl += 1
            while arr[pr] &amp;gt; pivot: pr -= 1
            if pl &amp;lt;= pr:
                arr[pl], arr[pr] = arr[pr], arr[pl]
                pl += 1
                pr -= 1
    
        if left &amp;lt; pr: qsort(arr, left, pr) # pr이 a[0]보다 오른쪽에 위치하면 왼쪽 그룹 나누기
        if pl &amp;lt; right: qsort(arr, pl, right) # pl이 a[8]보다 왼쪽에 위치하면 오른쪽 그룹 나누기

def quick_sort(arr):
    qsort(arr, 0, len(arr) - 1)

print(&quot;퀵 정렬을 수행합니다.(원소 수가 9 미만이면 단순 삽입 정렬을 합니다)&quot;)
num = int(input(&quot;원소 수를 입력하세요.: &quot;))
array = [None] * num

for i in range(num):
    array[i] = int(input(f&quot;array[{i}]: &quot;))

quick_sort(array)

print(&quot;오름차순으로 정렬했습니다.&quot;)

for i in range(num):
    print(f&quot;array[{i}] = {array[i]}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;sorted( ) 함수의 사용&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1657007020621&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;a, b = sorted([a, b])					# a, b를 오름차순으로 정렬
a, b, c = sorted([a, b, c])				# a, b, c를 오름차순으로 정렬
a, b, c, d = sorted([a, b, c, d])			# a, b, c, d를 오름차순으로 정렬

a, b = sorted([a, b], reverse = True)			# a, b를 오름차순으로 정렬
a, b, c = sorted([a, b, c], reverse = True)		# a, b, c를 오름차순으로 정렬
a, b, c, d = sorted([a, b, c, d], reverse = True)	# a, b, c, d를 오름차순으로 정렬&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시에서는 변수를 나열한 리스트를 sorted( ) 함수에 전달하고, 반환된 리스트를 풀어서 변수에 대입한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sorted( ) 함수는 오름차순을 기본으로 하지만, reverse에 True 값을 넘겨주면 내림차순 정렬을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 튜플은 이뮤터블의 속성(값이 변하지 않음)을 가지므로 튜플 자체를 정렬할 때는 다음과 같은 방법을 사용해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;1단계&lt;/b&gt;: sorted( ) 함수로 정렬한 원소의 나열에서 새로운 리스트를 생성한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;2단계&lt;/b&gt;: 생성한 리스트를 튜플로 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1657007247556&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; x = (1, 3, 2)
&amp;gt;&amp;gt;&amp;gt; x = tuple(sorted(x))
&amp;gt;&amp;gt;&amp;gt; x
(1, 2, 3)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>자료구조, 알고리즘 입문/정렬 알고리즘</category>
      <author>sungw00</author>
      <guid isPermaLink="true">https://vibeee.tistory.com/48</guid>
      <comments>https://vibeee.tistory.com/48#entry48comment</comments>
      <pubDate>Sun, 22 Sep 2024 22:04:11 +0900</pubDate>
    </item>
  </channel>
</rss>