diff --git a/Gemfile b/Gemfile index 1291498971d7..4e61c3189c62 100644 --- a/Gemfile +++ b/Gemfile @@ -1,2 +1,3 @@ source "https://rubygems.org" -gemspec \ No newline at end of file +gemspec +gem "webrick", "~> 1.7" diff --git a/_config.yml b/_config.yml index 1da3e705de83..259929a27791 100644 --- a/_config.yml +++ b/_config.yml @@ -15,24 +15,24 @@ minimal_mistakes_skin : "default" # "air", "aqua", "contrast", "dark", "dirt", "neon", "mint", "plum", "sunrise" # Site Settings -locale : "en-US" -title : "Site Title" -title_separator : "-" +locale : "ko-KR" +title : "안녕,설리!" +title_separator : "|" subtitle : # site tagline that appears below site title in masthead -name : "Your Name" -description : "An amazing website." -url : # the base hostname & protocol for your site e.g. "https://mmistakes.github.io" +name : "Sulley" +description : "안녕 설리! 블로그 입니다" +url : "https://meang123.github.io" baseurl : # the subpath of your site, e.g. "/blog" repository : # GitHub username/repo-name e.g. "mmistakes/minimal-mistakes" teaser : # path of fallback teaser image, e.g. "/assets/images/500x300.png" logo : # path of logo image to display in the masthead, e.g. "/assets/images/88x88.png" masthead_title : # overrides the website title displayed in the masthead, use " " for no title -# breadcrumbs : false # true, false (default) +breadcrumbs : true # true, false (default) words_per_minute : 200 comments: - provider : # false (default), "disqus", "discourse", "facebook", "staticman", "staticman_v2", "utterances", "giscus", "custom" + provider : "disqus" # false (default), "disqus", "discourse", "facebook", "staticman", "staticman_v2", "utterances", "giscus", "custom" disqus: - shortname : # https://help.disqus.com/customer/portal/articles/466208-what-s-a-shortname- + shortname : "Hi Sullivan" discourse: server : # https://meta.discourse.org/t/embedding-discourse-comments-via-javascript/31963 , e.g.: meta.discourse.org facebook: @@ -75,7 +75,7 @@ google: # SEO Related google_site_verification : bing_site_verification : -naver_site_verification : +naver_site_verification : "030497dd1cc7a51bf72c69577c31500d29e3598b" yandex_site_verification : baidu_site_verification : @@ -96,23 +96,23 @@ social: # Analytics analytics: - provider : false # false (default), "google", "google-universal", "google-gtag", "custom" + provider : "google-gtag" # false (default), "google", "google-universal", "google-gtag", "custom" google: - tracking_id : - anonymize_ip : # true, false (default) + tracking_id : "G-6FH90EBH5C" + anonymize_ip : false # Site Author author: - name : "Your Name" + name : "Sulley" avatar : # path of avatar image, e.g. "/assets/images/bio-photo.jpg" - bio : "I am an **amazing** person." - location : "Somewhere" - email : + bio : "공부한 자료 정리하는 사이트입니다." + location : "Korea" + email : "meangsungjoo@naver.com" links: - label: "Email" icon: "fas fa-fw fa-envelope-square" - # url: "mailto:your.name@email.com" + url: "meangsungjoo@naver.com" - label: "Website" icon: "fas fa-fw fa-link" # url: "https://your-website.com" @@ -253,20 +253,21 @@ whitelist: category_archive: type: liquid path: /categories/ + tag_archive: type: liquid path: /tags/ -# https://github.com/jekyll/jekyll-archives -# jekyll-archives: -# enabled: -# - categories -# - tags -# layouts: -# category: archive-taxonomy -# tag: archive-taxonomy -# permalinks: -# category: /categories/:name/ -# tag: /tags/:name/ + #https://github.com/jekyll/jekyll-archives + jekyll-archives: + enabled: + - categories + - tags + layouts: + category: archive-taxonomy + tag: archive-taxonomy + permalinks: + category: /categories/:name/ + tag: /tags/:name/ # HTML Compression @@ -287,6 +288,7 @@ defaults: layout: single author_profile: true read_time: true - comments: # true + comments: true share: true related: true + show_date: true diff --git a/_data/navigation.yml b/_data/navigation.yml index 6f30866f3bed..463c47235537 100644 --- a/_data/navigation.yml +++ b/_data/navigation.yml @@ -1,7 +1,22 @@ # main links main: - - title: "Quick-Start Guide" - url: https://mmistakes.github.io/minimal-mistakes/docs/quick-start-guide/ + - title: "Category" + url: /categories/ + - title: "Tag" + url: /tags/ + - title: "Search" + url : /search/ + + +docs: + - title: "목차" + children: + - title: "Category" + url : /categories/ + - title: "Tag" + url : /tags/ + + # - title: "About" # url: https://mmistakes.github.io/minimal-mistakes/about/ # - title: "Sample Posts" diff --git a/_pages/category-archive.md b/_pages/category-archive.md new file mode 100644 index 000000000000..3cd6f6e06761 --- /dev/null +++ b/_pages/category-archive.md @@ -0,0 +1,7 @@ +--- +title: "Category" +layout: categories +permalink: /categories/ +author_profile: true +sidebar_main: true +--- \ No newline at end of file diff --git a/_pages/search.md b/_pages/search.md new file mode 100644 index 000000000000..c32ce00347d0 --- /dev/null +++ b/_pages/search.md @@ -0,0 +1,5 @@ +--- +title: Search +layout : search +permalink: /search/ +--- \ No newline at end of file diff --git a/_pages/tag-archive.md b/_pages/tag-archive.md new file mode 100644 index 000000000000..58ae8e76afc6 --- /dev/null +++ b/_pages/tag-archive.md @@ -0,0 +1,7 @@ +--- +title: "Tag" +layout: tags +permalink: /tags/ +author_profile: true +sidebar_main: true +--- \ No newline at end of file diff --git a/_posts/2022-01-08-first.md b/_posts/2022-01-08-first.md new file mode 100644 index 000000000000..c532dd33447b --- /dev/null +++ b/_posts/2022-01-08-first.md @@ -0,0 +1,15 @@ +--- +layout: single +title: "첫 포스팅 입니다" +categories: introduction +toc: true +author_profile: false +sidebar: + nav : "docs" +search: false +--- +# 안녕하세요 + +공부한 자료를 정리하는 블로그입니다. + +감사합니다. diff --git a/_posts/2022-01-26-algoIntro.md b/_posts/2022-01-26-algoIntro.md new file mode 100644 index 000000000000..7cb6545214f8 --- /dev/null +++ b/_posts/2022-01-26-algoIntro.md @@ -0,0 +1,62 @@ +--- +layout: single +title: "알고리즘 소개 및 방향" +categories: introduction +tag : Algorithm +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +### 소개글 + +제가 공부한 내용을 정리하는 포스팅입니다. + +공부 진행할때 "코드없는 프로그래밍" 유튜브를 참고하면서 공부했습니다. + + +[코드없는 프로그래밍](https://www.youtube.com/channel/UCHcG02L6TSS-StkSbqVy6Fg) +{: .notice--danger} + + + +### 알고리즘 카테고리의 방향성 + +소개글에서 언급한 유튜버님의 영상을 기반으로 공부를 하였기 때문에 진행방식도 코드없는 프로그래밍님 방식을 따라갑니다. (하지만 문제에 대한 코드 흐름은 같지 않습니다.) + +알고리즘 개념에 초점이 맞추어져 있기보다 관련 문제를 풀어보고 리뷰하면서 +진행 할 예정입니다. + +사용 언어는 c++을 주로 사용합니다. +그래서 개인적으로 c++을 이용한 문제 풀이 및 리뷰 그리고 개인적인 생각을 정리하는 방향으로 진행이 됩니다. + +"코드없는 프로그래밍" 유튜브로 들어가시면 colab에 파이썬 코드가 있습니다. +파이썬 코드가 필요하신 분은 링크로 들어가셔서 보시면 좋을 것 같습니다. + + + + + +#### 정리 + +* 정리 + + 문제위주의 리뷰 블로그 + + + 사용어는 c++ , python + + + 알고리즘 개념 설명도 간단하게 언급 될 예정 + + + 유튜브를 공부했다 하더라도 생각의 흐름과 코드는 다름 + + + + + +### 마무리 + +이 포스팅은 개인 공부한 자료를 정리하는 느낌입니다. +그래서 내용이 정확하지 않거나 틀린 내용이 포함되어있을 수 있다는점 알려드립니다. +혹시 보시다가 이러한 내용이 보인다면 댓글로 알려주세요 바로 고치도록 하겠습니다. +감사합니다. \ No newline at end of file diff --git a/_posts/2022-01-26-array.md b/_posts/2022-01-26-array.md new file mode 100644 index 000000000000..1f48de7835d0 --- /dev/null +++ b/_posts/2022-01-26-array.md @@ -0,0 +1,1706 @@ +--- +layout: single +title: "배열관련 문제 " +categories: Array +tag: [c++,python,Algorithm,LeetCode,Array,moveZeros,Find pivot index,Minimum size subarray sum,Merge sorted Array,Find peak element,Merge intervals ,Shortest Unsorted continuout subArray ,Find duplicate number ,Two sum,Rotate Image 2D array ,search 2D matrix] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +### Intro + +풀어볼 문제는 총 11한 문제입니다. 링크 달았으니까 참고 자세한 문제 설명은 참고하시면 될것같습니다. + +이번 포스트에서는 총 11한 문제에 대한 저의 코드와 생각들을 정리합니다. + +내용이 길어질수있으니 문제 목록을 참고하셔서 필요한 부분만 보는것을 추천합니다. + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + +### 배열 정리 + +읽기를 할때 가장 효율이 좋은 자료 구조인 것 같습니다. +첫 주소를 알고 있으므로 index만 안다면 한 단계로 읽을수있다는 장점이 있습니다. + +삽입을 할 때 또한 마지막 요소에 삽입하는 경우 한 단계로 삽입할수있습니다. + +
+
+ +> 왜 마지막 요소에 삽입할때 O(1)인가 하면 배열에서 삽입하는 경우 요소들을 모두 복사 하는데 처음 요소에 삽입하는 경우 모든 요소 복사 해야 한다. 중간에 삽입하는 경우도 마찬가지로 중간 요소 부터 삽입 해야 한다 그러니 마지막 요소에 삽입 하는 경우에는 복사할 요소가 없으니 O(1)에 실행 가능 한것이다 보통은 그러는데 가끔 O(n)이 될수도 있다 특히 vector처럼 dynamic array를 사용 한다고 하자, vector는 size포인터,첫 번째 요소 가리키는 포인터,capcity포인터 정보를 기본적으로 가지고 있다 하지만 size가 부족해지면 capacity는 보통 2배까지 추가 확보 한다 확보한 공간에 다시 copy할수있기 때문이다 즉 마지막 요소에 삽입할때 하필 size가 부족해서 capacity가 변경이 된다면 copy가 발생하여 O(n)이 될수있다는 의미이다. 그래서 **vector같은 dynamic array 사용할때는 reserve() 명령어로 미리 할당 받는게 중요하다!** + +
+
+ + +하지만 요소를 앞 또는 중간에 삽입 , 검색, 삭제 하는 경우 최대 N의 시간이 걸릴수있습니다. + + +배열 문제를 풀때는 index를 다루는 것에 익숙해질 필요가 있었습니다. + + +#### 23/5/22 추가 내용 + +또한 dynamic array로 vector 많이 사용하게 될것인데 (vector 따로 정리한 내용있다 참고 하면 될것 같다) O(n)이 발생하지 않게 reserve로 미리 할당하는게 중요하다 + +11번 문제 이후로 array문제 추가 해서 업데이트 하겠습니다! + + + + + +### leetcode 704번 binary serach 연습 문제 + + +문제 들어가기전에 가장 기본적인 문제 하나 풀어 보면 좋을것 같아서 추가 한다 - 23/5/22 + + + + +[binary search](https://leetcode.com/problems/binary-search/) +{: .notice--danger} + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +### 풀어볼 문제 링크 LeetCode + +[추가 fascinating](https://leetcode.com/problems/check-if-the-number-is-fascinating/submissions/) +{: .notice--danger} + + +[1번_moveZeros](https://leetcode.com/problems/move-zeroes/) +{: .notice--danger} + + +[2번_Find_pivot_index](https://leetcode.com/problems/find-pivot-index/) +{: .notice--danger} + + +[3번_Minimum_size_subarray_sum](https://leetcode.com/problems/minimum-size-subarray-sum/) +{: .notice--danger} + +[추가 백준 14246 K보다 큰 구간](https://www.acmicpc.net/problem/14246) +{: .notice--danger} + +[추가 leetcode smaller than cur num](https://leetcode.com/problems/how-many-numbers-are-smaller-than-the-current-number/description/) +{: .notice--danger} + + +[4번_Merge_sorted_Array](https://leetcode.com/problems/merge-sorted-array/) +{: .notice--danger} + + +[5번_Find_peak_array](https://leetcode.com/problems/find-peak-element/) +{: .notice--danger} + +[추가 find peak array2]() +{: .notice--danger} + +[6번_Merge_Intervals](https://leetcode.com/problems/merge-intervals/) +{: .notice--danger} + +[7번_Shortest_Unsorted_continuout_subArray](https://leetcode.com/problems/shortest-unsorted-continuous-subarray/) +{: .notice--danger} + +[8번_Find_duplicate_number](https://leetcode.com/problems/find-the-duplicate-number/) +{: .notice--danger} + +[9번_Two_sum](https://leetcode.com/problems/two-sum/) +{: .notice--danger} + +[10번_Rotate_Image_2D_array](https://leetcode.com/problems/rotate-image/) +{: .notice--danger} + +[11번_search_2D_matrix](https://leetcode.com/problems/search-a-2d-matrix/) +{: .notice--danger} + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +### 추가 fascinating + +정수 n, n*2, n*3을 이어 붙혔을때 1-9까지 digit 중복되면 안되고 0이 없는 거를 fascinating이라고 한다 fascinating이면 T 아니면 F 하는 문제 였다 + +내가 푼 방식은 중복을 알아보기 위해 string 123456789를 통해 중복 확인 해주었다 그리고 0이 왔을때 인덱스 처리 하면 -1이 나오니까 이때 false를 return 하도록 하였다 +간단하게 풀기 좋은 문제 같다 + +
+
+ + + +```c++ +class Solution +{ +public: + bool isFascinating(int n) + { + + string temp = "123456789"; + string s = to_string(n)+to_string(n*2)+to_string(n*3); + + for(auto&e:s) + { + if((e-48)-1==-1||temp[(e-48)-1]=='0') + { + return false; + } + temp[(e-48)-1]='0'; + + } + return true; + } +}; +``` + + + + + + +### 1 번 moveZeros + + +다른 배열을 copy해서 처리 하면 더 쉽게 풀리지만, 문제에서 Space Complex가 상수로 해결 하라는 조건이 있습니다. + +그래서 입력된 배열을 그대로 출력 공간을 사용해야합니다. + +0을 만날때 마다 배열에서 삭제 /삽입하는 것은 배열에서 효율적이지 않습니다. + +문제의 핵심은 0을 가리키는 index1, 0이 아닌 요소 가리키는 index2 두개의 index설정하는 것입니다. + + +* c ++ 코드 + + +```c++ + +class Solution { +public: + void moveZeroes(vector& nums) + { + //0을 가리키는 index + int idx = 0; + + for (int i = 0; i < nums.size(); i++) + { + if (nums[i] != 0) + { + swap(nums[idx], nums[i]); + idx++; + } + } + } +}; + + +``` +위에 방법 처럼 풀면 1,1 처럼 연속적인 0 아닌 값 나왔을때도 자기 자신과 swap 하게 된다 이를 개선 한 방법은 다음의 코드이고 two pointe를 사용 했다. + + +```c++ +class Solution { +public: + void moveZeroes(vector& nums) + { + if(nums.size()==1) + { + return; + } + int idx1=0,idx2=1; + + while(idx2 != nums.size()) + { + + //idx2와 idx1 사이에 0이 있으면 안된다 오직 0이 아닌 연속적인 값만 고려 하는 조건 식이다 + if(idx2-idx1==1&&nums[idx1]!=0&&nums[idx2]!=0) + { + idx1++; + idx2++; + continue; + } + + + if(nums[idx1]!=0) + { + idx1++; + } + else if(nums[idx2]!=0) + { + nums[idx1]=nums[idx2]; + nums[idx2]=0; + + } + + idx2++; + } + } +}; +``` + +**two pointer 방식으로 다시 풀어보면서 느낀점 두 포인터 간의 거리도 이용하면 연속적인 숫자, 거리를 조절 할수 있다는 것을 알게 되었다** + + + +개선 한 방법이 leetcode 해보았을때 더 빠르다 + + + + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + +### 2번 Find pivot index + +pivot 기준으로 왼쪽 합과 오른쪽 합이 같이지는 index를 반환하는 문제입니다. + +우리가 문제에서 찾아내야하는 것은 pivot index입니다. 배열 처음 부터 pivot index인지 볼것이기 때문에 +pivot index기준 왼쪽 합과 오른쪽의 합을 알아야합니다. + +pivot index는 배열 처음 부터 시작하므로 + +//초기화 조건 +left_sum =0; +right_sum = 배열의 총합으로 초기화 할 수 있습니다. + +문제를 분석해보면 right sum 과 left sum이 같아질때 한박자 느리다는거 이게 핵심이다!! +그리고 또 다른 분석은 pivot index 기준으로 값이 같다는 성질 즉 양쪽 값이 pivot index 기준으로 같다는 말 + +* c++ 코드 + +```c++ +class Solution { +public: + int pivotIndex(vector& nums) + { + //배열의 총합을 구한다 + int total = accumulate(nums.begin(), nums.end(), 0); + + //초기화 + int left_sum = 0; + int right_sum = total; + int prev = 0; + + for (int i = 0; i < nums.size(); i++) + { + right_sum -= nums[i]; + left_sum += prev; + + if (left_sum == right_sum) + { + return i; //pivot index + } + prev = nums[i]; + } + + //pivot index없는 경우 + return -1; + } +}; + +``` + +* python 코드 + +```python +class Solution(object): + def pivotIndex(self, nums): + + """ + :type nums: List[int] + :rtype: int + """ + total_sum = sum(nums) + + right_sum = total_sum + left_sum = 0 + + for idx, num in enumerate(nums): + right_sum -= num + if left_sum == right_sum: + return idx + left_sum += num + + return -1 +``` + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +### 3번 Minimum Size Subarray Sum + +연속된 배열의 합 == target 중에서 길이가 가장 짧은 길이를 구하는 문제입니다. + +연속된 subArray의 합을 구하는 것이므로 진행 방향은 첫 요소 부터 진행하다가 +조건이 안맞으면 다음 요소 즉 두번째 요소부터 진행하는 방식으로 진행합니다. 두 번째 요소부터 시작할때 다시 더 헤줄 필요없이 첫번째 요소의 element만 빼주면 두 번째 element부터 더한것을 유지 할 수 있습니다. + +그리고 값이 target과 같아지거나 커지면 그때의 길이를 min함수로 업데이트합니다. + + +* c++ 코드 + +```c++ +int minSubArrayLen(int target, vector& nums) +{ + int left = 0; + int right = 0; + int sum = 0; + int len = INT_MAX; + + while (right < nums.size()) + { + sum += nums[right]; + + while (sum >= target) //right고정 될때 left 가 조절되어야하기 때문이다 + { + len = min(len, right - left + 1); + sum -= nums[left]; + left++; + } + right++; + } + + //len이 초기값과 같다면 0 반환한다 + return (len==INT_MAX) ? 0 : len; +} +``` + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + + +### 추가 문제 백준 14246 K보다 큰 구간 + + +특정 구간의 합이 target k보다 큰 모든 쌍의 개수를 출력하는 문제 였다 +two pointer를 사용해서 문제를 해결 하였다 sum이 k보다 클때 뒤에 나오는 구간들도 모두 포함외 되므로 계산에 포함 시켰다 그리고 계속 해맸던것이 idx1이 sum>k의 조건이 아닐때까지 반복문을 돌렸어야 했는데 이걸 찾는데 오래 걸렸다. 아래는 내가 푼 문제의 코드이다 그리고 sum과 sub_count는 문제 조건에 맞추어서 long long int 해줌 이거 안하면 틀린걸로 판단 한다 사실 여기서 오래 걸렸다 (분명 맞는것 같은데 틀렸다고 나와서 계속 시도 하다가 long long int로 바꾸었더니 되었다 백준 싫어...) + + +```c++ +#include +#include + + +using namespace std; + +int main() +{ + ios::sync_with_stdio(false); + cin.tie(NULL); + + int n = 0, k = 0, idx1 = 0, idx2 = 0; + long long int sum = 0, sub_count = 0; + cin >> n; + + vector nums(n); + for (int i = 0; i < n; i++) + { + cin >> nums[i]; + } + + cin >> k; + + + //idx1과 idx2가 같은곳 가리켜도 괜찮음 + + for(idx2;idx2=k일때까지 반복문 돌려 줘야 한다 + //해맬때는 while대신에 if로 했었는데 사실 if-> while바뀐것 밖에 없는데 시간이 오래 걸렸다.. + while (sum > k) + { + sub_count += nums.size() - idx2; + + sum -= nums[idx1]; + idx1++; + + } + + + + } + + cout < smallerNumbersThanCurrent(vector& nums) + { + vector vec; + + for(int i=0;inums[j]) + { + count++; + } + } + vec.emplace_back(count); + + } + return vec; + + + } +}; + +``` + +하지만 time :O(n)으로 할수가 있었다 그런 sort,hashmap을 사용하는것이다. +정렬 시킨 ans 배열을 hash map인 m에 emplace할껀데 이때 중복된 숫자는 하나로 처리 할수있도록 한다 그리고 핵심은 정렬시킨 ans요소를 큰 요소값 부터 넣어주는것이다 +이렇게 하면 큰 숫자 a보다 작은 요소개수를 알수가 있다 직접 해보면 무슨말인지 알수있다 어쨌든 인덱스를 뒤에서 부터 i= nums.size()-1부터 큰 요소값과 mapping해주면 현재 요소보다 작은 값의 개수를 알수가 있다 + + +```c++ +class Solution { +public: + vector smallerNumbersThanCurrent(vector& nums) { + vector ans = nums; + unordered_map m; + sort(ans.begin(), ans.end()); + for(int i=nums.size()-1; i>=0; i--) + m[ans[i]] = i; + for(int i=0; i arr1을 반환하기 때문입니다 + +##### Hint +**0이 아닌 요소 index부터 비교를 시작해서 값이 큰 값 먼저 arr1배열 끝에 삽입합니다.** + + +만약 merge sort를 직접 구현 하고 배열을 합친다면 +Time : O(nlogn)의 복잡도로 구현 할 수 있습니다. + + +* c++ 코드 + +```c++ +class Solution +{ +public: + void merge(vector& nums1, int m, vector& nums2, int n) + { + + + int arr1_size = m - 1; //숫자 끝 부분 + int arr2_size = n - 1; + int idx = m + n - 1; //arr1의 끝 부분 + while (arr2_size >= 0) + { + if (arr1_size >= 0 && nums1[arr1_size] >= nums2[arr2_size]) + { + nums1[idx] = nums1[arr1_size]; + idx--; + arr1_size--; + + } + else + { + nums1[idx] = nums2[arr2_size]; + arr2_size--; + idx--; + } + } + } +}; +``` + +* python 코드 + +```python +class Solution(object): + def merge(self, nums1, m, nums2, n): + """ + :type nums1: List[int] + :type m: int + :type nums2: List[int] + :type n: int + :rtype: None Do not return anything, modify nums1 in-place instead. + + """ + idx = m + n - 1 + l_1 = m - 1 + l_2 = n - 1 + + while l_2 >= 0 and l_1 >= 0: + if nums2[l_2] > nums1[l_1]: + nums1[idx] = nums2[l_2] + l_2 -= 1 + + else: + nums1[idx] = nums1[l_1] + l_1 -= 1 + idx -= 1 +``` + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + +### 5번 Find Peak Element + + +O(N)으로 문제를 해결한다고 했을때는 sliding window를 3개 잡아서 주위값보다 큰지만 확인해 가면서 해결할수가 있다 하지만 문제에서 O(log N)으로 해결해야한다는 조건이 있습니다. + +O(log N)이면 binary search를 생각해 볼수있습니다. + +그래프를 그려보면 더 직관적으로 이해 할수가 있다 peak element처럼 꼭지점을 찾는거다 여기서 정렬되어있는 상황에서 binaray search를 생각 하지 말고 조금더 근본적으로 보면 이해하기 쉬울것이다 binary search는 우선 반으로 나누고 반으로 나눠서 요소를 찾는것이다 이 문제에도 크게 벗어나지 않는다 그래프 그려 보면 알겠지만 가장 큰 peak element 즉 극대점을 찾는게 아니다 local적으로 꼭지점있으면 그것도 정답에 포함이 된다 우선 mid값과 mid+1값을 비교해서 mid+1이 더 크면 꼭지점은 오른쪽에 있다고 보고 범위를 줄인다 만약 mid값이 더 크다면 반대로 범위를 왼쪽으로 줄여서 보면 된다 근본적으로 계속 반으로 범위를 설정해 가면서 원하는 요소 값을 찾는게 핵심인데 binary search 기본 내용에서 크게 벗어나지 않음을 알수가 있다 + + +그래서 binary serarch 구현과 비슷하게 코드를 구성 하면 아래와 같습니다. + + + +* c++ 코드 + +```c++ +class Solution { +public: + int findPeakElement(vector& nums) + { + //배열 크기가 1보다 작으면 0을 반환 + if (nums.size() <= 1) + { + return 0; + } + + //index초기화 합니다 + int left = 0; + int right = nums.size() - 1; + + //가운데 기준으로 peek element 찾는다 -> binary search + while (left < right) + { + int pivot = (left + right) / 2; + if (nums[pivot] < nums[pivot + 1]) + { + left = pivot + 1; + } + else + { + right = pivot; //right도 포함해야한다 + } + } + return left; + } +}; +``` + + + +# 13 Find peak element 2 leetcode + +위에서는 left-right만 파악하면 되었는데 grid라서 top-bottom도 신경 써야 하는 문제이다. +
+
+ + +위의 문제를 풀어보고나서 풀어보았다 내가 푼 방법은 binary search방식으로 풀지 않았다 그래서 사용해서 최적화 하는 코드도 같이 업데이트 하겠다 +<추후에 업데이트> + +내가 문제를 해결한 방법 binary search사용시 peak element를 전부 파악할수없으니까 sliding window 방식을 사용했다 그렇게 함으로써 left-right peak element해당하는 요소를 따로 배열로 정리를 하였고 이 요소를 가지고 top-bottom을 판단하였다 만약 top-bottom 조건에도 맞다면 return하도록 하였다 +2차원 배열 요소에 대한 index를 파악하기 위해 unordered_map을 사용했다 어쨌든 해결한 큰 틀은 위에와 같고 **binaray search 방식으로 최적화 하는 코드는 맨 밑에 추가 하겠다 현시점23/06/21에는 아직 안함 -- 업데이트시에는 지우기** + +
+
+ +**처음에 내가 풀었던 코드** + +```c++ + class Solution + { + public: + + bool judge(int left, int mid, int right) + { + if (leftright) + { + return true; + } + return false; + }; + + vector findPeakGrid(vector>& mat) + { + unordered_map> m; + + vector temp = { -1 }; + + //O(N) + for (int i = 0; i < mat.size(); i++) + { + for (int j = 0; j < mat[0].size(); j++) + { + m.emplace(mat[i][j], make_pair(i, j)); + temp.emplace_back(mat[i][j]); + } + } + + + temp.emplace_back(-1); + vector temp2; + + //아직까지 O(N) + for (int i = 0; i + 2 < temp.size(); i++) + { + if (temp[i]temp[i + 2]) + { + temp2.emplace_back(temp[i + 1]); + } + } + + //해당 요소만 top bottom 비교 + + for (auto& e : temp2) + { + int top = m[e].first - 1; + int bottom = m[e].first + 1; + int left = 0, right = 0; + if (top == -1) + { + left = -1; + right = mat[bottom][m[e].second]; + if (judge(left, e, right)) + { + return vector {m[e].first, m[e].second}; + } + + } + else if (bottom == mat.size()) + { + right = -1; + left = mat[top][m[e].second]; + if (judge(left, e, right)) + { + return vector {m[e].first, m[e].second}; + } + } + else + { + left = mat[top][m[e].second]; + right = mat[bottom][m[e].second]; + if (judge(left, e, right)) + { + return vector {m[e].first, m[e].second}; + } + + } + + + } + + + + + return vector {-1,-1}; + + } +}; + +``` +사실 시간 복잡도를 위에 코드에서 볼때 이해가 안되는게 처음 배열을 N만큼 순회 하는거 빼고 두번째 세번째 반복문은 N보다 작은 반복문이다 그래서 최종으로는 배열을 한번 순환한 N이 나올꺼라고 생각을 했다 그래서 문제에서 요구하는 n log(m)이나 m log(n)보다 더 괜찮을것이라고 생각을 했다 물론 비교적 메모리는 많이 사용한것 같지만 시간 복잡도만 보았을때는 O(n)이 나올것으로 예상하고 제출했는데 겨우 통과 될 정도의 속도였음을 알았다 -- **시간 체크 하는거 chrono보고 비교 해보기** +
+
+ + +**최적화 적용한 코드 binary search** + +```c++ + + +``` + +**처음 코드와 두번째 코드 시간 비교 해보기** + + + + +> 위에 문제 목록 추가 하기 + + + + + + + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + +### 6번 Merge Intervals + +문제 이름대로 구간을 범위에 맞추어서 합치는 문제입니다. + + +intervals where intervals[i] = [starti, endi] + +구간은 위와 같이 start,end 순으로 입력이 됩니다 + +구간을 합치려면 end_first ,start_second 두개를 비교해서 구간을 합쳐야 한다는것을 알수있습니다. + +이렇게 진행을 하면 모든 요소를 다 비교를 해주어야 하기 때문에 O(n^2)이 걸립니다. + +하지만 start중심으로 정렬을 한다면 O(NlogN+M)으로 해결 할수있을것입니다. +왜냐하면 일일이 다 살펴 볼 필요없이 진행 할 수 있기 때문입니다. + + + +우선 이 문제를 보고 제가 푼 방식을 나열하고 코드를 보겠습니다 +참고로 제가 처음 생각한 코드는 실행은 되지만 시간 초과가 나왔기 때문에 결국 다른 방법으로 코드를 구현해야했습니다 + +첫번째는 구간을 합치는 조건을 생각했습니다. end_first >= start_second이면 구간을 합칠수있다고 생각했습니다. +두번째는 진행방식에 대해 생각했습니다. 구간 조건만 생각한다면 2중 반복문으로 모두 비교를 해주어야 하기 때문에 시간 초과가 날것입니다. 이를 위해 첫 요소를 정렬 시켜 주는 방식을 사용했습니다. +세번째는 구현 방법을 생각 했습니다. 아래 코드를 보면서 부연 설명 하겠습니다. +결론적으로 i번째 요소를 지우면서 진행하는 방향으로 생각했는데 vector.erase()함수를 사용해서 아마 시간 초과가 난것 같습니다. +erase함수의 시간 복잡도는 O(n)이기 때문이고 for 문 안에서 사용하였기 때문에 O(n^2)이 넘어 간것 같습니다. + + + +그래서 새롭게 구현한것의 핵심은 지우는 방식을 바꾼것입니다. 배열에서 하나씩 땡겨오는 것처럼 구현을 했습니다. 이렇게 되면 마지막에는 구간이 합쳐진 개수만큼 뒤에 남을것이니까 합쳐진 만큼 pop_back하면 됩니다. + + +* c++ 코드 (시간 초과 코드) + +```c++ +class Solution +{ +public: + vector> merge(vector>& intervals) + { + sort(intervals.begin(), intervals.end()); //정렬 + + int size = intervals.size() - 1; + int idx = 0; //배열의 사이즈를 맞추기위한 변수 + for (int i = 0; i < size; i++) + { + if (intervals[i - idx][1] >= intervals[i + 1 - idx][0]) + { + + intervals[i + 1 - idx][0] = intervals[i - idx][0]; + intervals[i + 1 - idx][1] = max(intervals[i - idx][1], intervals[i+1- idx][1]); //범위가 큰쪽으로 맞추기 위함 + + intervals.erase(intervals.begin()+i - idx,intervals.begin()+i - idx +1); //O(n) + idx++; + } + } + + + return intervals; + } +}; +``` + + +* c++ 코드 + +```c++ +class Solution { +public: + vector> merge(vector>& intervals) + { + //start 기준으로 정렬 + sort(intervals.begin(), intervals.end()); + int idx = 0; + for (int i = 1; i < intervals.size(); i++) + { + + + if (intervals[idx][1] >= intervals[i][0]) + { + //조건이 맞으면 구간을 합친다 + intervals[idx][1] = max(intervals[i][1], intervals[idx][1]); + + } + else + { + idx++; + intervals[idx] = intervals[i]; + } + + } + + //합쳐서 줄어든 만큼 pop하는 코드 intervals.size()가 항상 큰상황이다 + while (intervals.size() != idx+1) + { + intervals.pop_back(); + } + + + return intervals; + } +}; +``` + + +처음 풀었던 방식과 두번째 방식에서 논리는 어느정도 일치 하지만 구간 삭제 하는 방식에서 차이가 있었다 erase로 O(n)복잡도로 삭제 하지 말고 O(1)으로 삭제 할수있게 두 포인터 조절 하는 감을 배울수있었다 + + + + +* python 코드 + +```python +class Solution(object): + def merge(self, intervals): + """ + :type intervals: List[List[int]] + :rtype: List[List[int]] + """ + + #첫요소 기준으로 정렬하는 코드 + intervals.sort(key=lambda x:x[0]) + + idx =0 + for i in range(1,len(intervals)): + if intervals[idx][1]>=intervals[i][0]: + intervals[idx][1]=max(intervals[idx][1],intervals[i][1]) + + else: + idx+=1 + intervals[idx]=intervals[i] + + while(len(intervals)!=idx+1): + intervals.pop() + + return intervals + +``` + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + +### 7번 Shortest Unsorted Continuous Subarray + +이번 문제 같은 경우는 정렬을 이용해서 문제를 해결하셔도 되지만 +TIME 은 O(NlogN)이 걸릴것입니다. 만약 TIME 을 O(N)으로 해결하고 싶다면 그래프를 그려보고 경향성을 따져 보아야합니다. + +LeetCode에 있는 Solution중 stack을 이용해서 푼 방법이 있는데 그 풀이 방식이 경향성을 따지고 푼 방식입니다. +하지만 저의 코드는 stack을 이용하는 대신 반복문을 이용해서 문제를 해결했습니다. +아래 코드는 O(n)의 시간 복잡도를 가집니다. 문제 풀이 방식은 최솟값과 최댓값을 찾아서 이어주는 방식이라기 보다는 그래프로 그려 보았을때 +처음 꺽이는 지점, 마지막의 꺽이는 지점을 찾아서 인덱스 계산 한것입니다. + + +* c++ 코드 + +```c++ + +class Solution { +public: + int findUnsortedSubarray(vector& nums) + { + if (nums.size() < 2) + { + return 0; + } + + int end = 0; + int prev = nums[0]; + + //꺽이는 시작점 찾기 O(n) + for (int i = 0; i < nums.size(); i++) + { + if (nums[i] < prev) + { + end = i; + } + else + { + prev = nums[i]; + } + } + + int start = nums.size() - 1; + prev = nums[start]; + + //꺽이는 끝지점 찾기 O(n) + for (int i = nums.size() - 1; i >= 0; i--) + { + if (nums[i] > prev) + { + start = i; + } + else + { + prev = nums[i]; + } + } + + if (end != 0) + { + return end - start + 1; + } + return 0; + + } +}; +``` +* python code +```python +from typing import List + +class Solution: + def findUnsortedSubarray(self, nums: List[int]) -> int: + + if len(nums) < 2: + return 0 + end = 0 + prev = nums[0] + for i in range(len(nums)): + if nums[i] < prev: + end = i + else: + prev = nums[i] + + start = len(nums)-1 + prev = nums[-1] + + for i in range(len(nums))[::-1]: + if nums[i] > prev: + start = i + else: + prev = nums[i] + + + if end != 0: + return end-start+1 + + return 0 + +``` + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + +### 8번 Find Dupicate + + +중복된 숫자를 찾는 문제입니다. Hash Map을 이용해서 풀면 쉽게 문제를 해결 할 수 있습니다. + +하지만 SPACE를 O(N)으로 해결 해야한다면 Hash Map을 사용 할 수 없게 됩니다. 이런 경우에는 +/- 성질을 이용해서 문제를 풀면 좋습니다. + +입력으로 오는 배열의 값에 -1을 곱해줍니다. 만약 중복된 값이 있다면 음수는 양수가 될것입니다. 그러면 중복된 값을 찾게 된것이니 반환해주면 됩니다. Hash Map을 이용한 풀이와 사용하지 않은 풀이 코드는 아래와 같습니다. + + +* c++ Hash Map 사용한 코드 + + +```c++ +class Solution { +public: + int findDuplicate(vector& nums) + { + // + if (nums.size() == 0) + { + return 0; + } + unordered_map map; + + for (int i = 0; i < nums.size(); i++) + { + + if (map.find(nums[i]) != map.end()) + { + return nums[i]; + } + map[nums[i]]++; + } + + } +}; +``` + + +* c++ Hash Map 사용하지 않은 코드 + +```c++ +class Solution { +public: + int findDuplicate(vector& nums) + { + for(int i=0;i0) + { + return tmep_pos+1; + } + } + return -1; + + } +}; + +``` +위와 같은 코드로 만들수있는 이유는 문제에서 [1,n]까지의 범위에서 반복된 숫자 하나만 찾으라는 문제였기 때문에 인덱스로 접근해서 문제를 풀수있는것이다. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + +### 9번 Two Sum + +이번 문제는 two pointer를 이용해서 푸는 문제입니다. 배열에 대한 감을 잡기 좋은 문제입니다. two sum 뿐만아니라 3,4,..N sum 문제 또한 LeetCode에 있으니까 필요하신 분은 찾아서 풀어보는것을 추천합니다. + + +**풀이 방법** +1. 정렬을 이용해서 푸는 방법 + + * 정렬을 하고 양쪽의 two pointer를 잡아서 target을 찾을수있습니다. + +2. hash map을 이용해서 푸는 방법 + + * Hash map key : target - arr[n] + Hash map value: index + + * 해당 값을 찾으면 반환 한다 + + + +정렬을 이용해서 문제를 푸신다면 , 한 가지 주의 할 점은 바뀐 index가 아니라 입력한 배열 index 그대로 유지를 해야한다는 점입니다. +정렬 하기 전에 입력 배열의 index를 따로 저장을 하고 two sum 풀이를 이어나가야 합니다. + + +Hash map을 이용한 풀이에서 핵심은 target - arr[n]을 key값으로 넣는 것 입니다. 만약 key값이 hash map에 있다면 그 값이 우리가 찾는 값입니다. + +* c++ 코드 정렬 사용 + +```c++ +class Solution { +public: + vector twoSum(vector& nums, int target) + { + //정렬 되었을때 index 유지 하기 위함이다 + vector> temp; //first -> nums 요소 second -> index + + for (int i = 0; i < nums.size(); i++) + { + temp.emplace_back(make_pair(nums[i],i)); + + } + + //first인 nums[i]기준으로 정렬 + sort(temp.begin(), temp.end()); + + + int idx = 0; + int idx2 = nums.size() - 1; + + int start = 0, end = 0; + + while (idx < idx2) + { + //nums 배열의 요소로 비교 + if (temp[idx].first + temp[idx2].first == target) + { + //정렬하기 전의 index를 유지한다 + start = temp[idx].second; + end = temp[idx2].second; + break; + } + + if (temp[idx].first + temp[idx2].first > target) + { + idx2--; + } + else + { + idx++; + } + } + + + return {start,end}; + + } +}; + +``` + + +위와 비슷한 방법이지만 temp 배열을 조금 수정 했습니다. + +탐색하는 방식과 정렬하는것은 동일하지만 target을 찾고 인덱스를 찾는것에 조금 차이를 주었습니다. + +처음에는 find를 정방향으로 두번 썼는데 같은 값이면 첫 번째만 find가 찾기 때문에 하나는 정방향 하나는 역방향에서 찾게 바꾸었습니다. +그리고 역방향 find의 distance를 칮을때는 (temp.rend()-it2)-1 한번더 1을 빼주어야 함을 알수있었습니다. + +성능으로 따지면 위의 코드보다 좋지 않습니다. find의 시간 복잡도는 O(n)이니까요 -> 솔직히 temp 배열을 수정한것 밖에 차이가 없습니다 + +```c++ +class Solution { +public: + vector twoSum(vector& nums, int target) + { + vector temp = nums; + + sort(nums.begin(), nums.end()); + + int idx1 = 0, idx2 = nums.size()-1; + int temp_val = 0; + + + while (idx1 < idx2) + { + temp_val = nums[idx1] + nums[idx2]; + if (temp_val == target) + { + const auto it = find(temp.begin(), temp.end(), nums[idx1]); + //cout << "hello " << it - temp.begin() << endl; + + const auto it2 = find(temp.rbegin(), temp.rend(), nums[idx2]); + + return { int(it - temp.begin()),int((temp.rend()-it2)-1) }; + } + if (temp_val > target) + { + idx2--; + } + else + { + idx1++; + } + } + + } +}; +``` + + +* c++ 코드 Hash map 사용 + +```c++ +class Solution +{ +public: + vector twoSum(vector& nums, int target) + { + + // Hash map 사용 + unordered_map hash; //key : target-num[i] value: index + + vector result; + for(int i=0;i List[int]: + + temp = sorted(nums) + arr_idx_size = len(nums)-1 + idx,idx2=0,arr_idx_size + + while idx target: + idx2-=1 + else: + idx+=1 + + + return [] + + +``` + + +다음은 hash를 사용한 파이썬 코드 입니다 + +```python + +class Solution(object): + def twoSum(self, nums, target): + """ + :type nums: List[int] + :type target: int + :rtype: List[int] + + hash map -> dic사용하는 방법 + + O(n)으로 해결 가능 + """ + + value={} + + for idx,val in enumerate(nums): + + if val in value: + return [idx,value[val]] + + value[target-val]=idx + + +``` + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + + +### 10번 Rotate Image 2D array + + +이번 문제는 2차원 배열에 대한 감을 찾기 좋은 문제입니다. + +n*n 2차원 행렬을 90도 회전한 모습을 출력하는 문제입니다. +*단, 또다른 2차원 행렬의 공간을 만들어서는 안됩니다. 입력으로 들어온 matrix를 그대로 return 해야합니다.* + +4개의 지점이 같이 움직인다고 생각하면 이해하기 쉬울것입니다. + +n*n img matrix + +a b c a + -> +c d d b + +int temp = img[b] +img[b] = img[a] +img[a] = img[d] +img[d] = img[c] +img[c] = temp + + +* c++ 코드 + +```c++ + +void Rotate_img(vector>& arr) +{ + //n-1 + int len = arr[0].size()-1; + + //반절의 길이만큼 반복 + for (int row = 0; row < arr.size() / 2; row++) + { + for (int col = 0; col < (arr.size() + 1) / 2; col++) + { + + + int temp = arr[len - col][row]; + + arr[len - col][row] = arr[len - row][len - col]; + arr[len - row][len - col] = arr[col][len - row]; + arr[col][len - row] = arr[row][col]; + arr[row][col] = temp; + + + } + } +} + +``` + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +### 11번 Search a 2D Matrix I + + +문제 설명을 간단히 해보면 + +M*N 행렬이 다음과 같다고 하면 + +| 1 | 3 | 5 | 7 | +| ---- | ---- | ---- | ---- | +| 10 | 11 | 16 | 20 | +| 23 | 30 | 34 | 60 | + + +1. 각 행은 정렬되어있다 + + * 1 3 5 7 , 10 11 16 20 등 각 행은 정렬되어있다 + +2. 각 행의 첫 요소는 이전 행의 마지막 숫자보다 크다 + + * 10은 7보다 크고 23은 20보다 크다 + + + +이런 행렬일때 target값이 행렬안에 있는지 없는지 T/F 로 출력하면 되는 문제입니다. + + +저는 처음에 2번의 규칙을 활용해서 문제를 풀었습니다. 만약 target이 행의 마지막 요소보다 작다면 그 행에서 target을 찾으면 됩니다. + +아래는 직관적으로 문제를 푼 코드 입니다. + +* c++ 코드 직관적인 풀이 version + +```c++ + +class Solution { +public: + bool searchMatrix(vector>& matrix, int target) + { + //base case + if (matrix.size() == 0 || matrix[0].size() == 0) + { + return false; + } + + int end = matrix[0].size()-1; //각 행의 크기 + + for (int i = 0; i < matrix.size(); i++) + { + //각 행의 마지막 요소가 target보다 같거나 크면 -> 2번 규칙을 활용한 것입니다. + if (matrix[i][end] >= target) + { + //해당 row에서 target요소를 찾는다 만약 찾았다면 true 반환 + if (matrix[i].end() != find(matrix[i].begin(), matrix[i].end(), target)) + { + return true; + } + } + } + return false; + + } +}; + +``` + + +위의 풀이도 통과하는 방식이지만 조금 더 보안해서 문제를 풀수도 있습니다. + +* 1번 규칙과 2번 규칙을 다시 한번 봅시다. + * 1번 규칙을 통해 각 행은 정렬이 보장 되어있습니다. + * 2번 규칙을 통해 전의 행보다 지금의 행이 더 큰 값을 가지고 있음을 보장합니다 + +1 번과 2번을 통합해서 생각해보면 전체적으로 보았을때 오름차순 정렬 경향성이 보장 되어있음을 알수있습니다. + +이런 상황에서 target값을 찾는 문제 .... 이제 감이 오시나요? ....... + + + +Binary Search를 이용하기 적절한 상황임을 알수있습니다. 2차원 배열을 1차원 배열로 늘려서 생각 해봅니다. + +아래는 Binary Search로 문제를 푼 c++ 코드입니다. + + +* c++ Binary Serach 이용한 풀이 + +```c++ + +class Solution +{ +public: + bool searchMatrix(vector>& matrix, int target) + { + //base case + + if (matrix.size() == 0 || matrix[0].size() == 0) + { + return false; + } + + //Initialize m*n size matrix + int m = matrix.size(); + int n = matrix[0].size(); + + + //2차원 배열을 1차원 배열로 생각 했을때 길이입니다. + int left = 0; + int right = n * m - 1; + + + while (left <= right) + { + //mid = (left+right) / 2 와 동일합니다 OverFlow를 막기 위한 수학적 꼼수라고 생각하시면 좋습니다. + int mid = left + (right - left) / 2; + + if (matrix[mid / n][mid % n] == target) + { + return true; + } + else if (matrix[mid / n][mid % n] > target) + { + right = mid - 1; + } + else + { + left = mid + 1; + } + } + return false; + + } +}; + +``` + +* 위의 코드에서 핵심 + * 2차원 배열을 1차원 배열로 보기 + * "mid / n", "mid % n" 이해하기 + * mid는 2차원을 1차원으로 늘린것이므로 n size로 나누어서 몫과 나머지를 구하면 2차원 배열로 생각했을 때의 index를 구할수있습니다. + + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +### 12번 Search a 2D Matrix II + +11번 문제와 거의 똑같은 문제인데 2번의 규칙만 달라진 문제입니다. + + +**바뀐 2번 규칙** + +각 열은 정렬되어있다. + + +바뀐 2번 규칙을 사용해서 위에서 풀었던 문제를 풀면 됩니다. + +바뀐 2번 규칙 때문에 처음에 직관적으로 풀이는 더 이상 사용 할 수 없습니다. +또한 전체적으로 상승하는 경향성도 아니기 때문에 Binary Search도 사용 할 수 없습니다. + +예제 행렬) + +| 1 | 4 | 7 | 11 | 15 | +| ---- | ---- | ---- | ---- | ---- | +| 2 | 5 | 8 | 12 | 19 | +| 3 | 6 | 9 | 16 | 22 | +| 10 | 13 | 14 | 17 | 24 | +| 18 | 21 | 23 | 26 | 30 | + + +이번 문제 또한 2번 규칙의 성질을 잘 활용해서 문제를 풀어야 합니다. + +앞에 문제를 풀었던 것 처럼 1번 규칙과 2번 규칙을 통해 해결 방안을 생각해 보겠습니다. + +**각 행과 열은 각각 정렬 되어있다** + +만약에 찾는 target이 16이라고 생각 해봅시다 + +18 요소에서 탐색을 시작해봅시다. + +그러면 각 행과 열은 각각 정렬 되어 있다는 사실 덕분에 + +* *18이 포함된 행* + 즉 18 21 23 26 30 에는 찾는 target이 없다는것을 보장 할수있습니다. + +그러면 행을 올릴수 있습니다. 다시 10요소에서 target을 찾기를 반복합니다. + +10은 16 보다 작습니다. 그러면 찾는 target은 10이 포함된 열에 포함이 되지 않았음을 보장 할 수 있습니다. + +그러면 13으로 넘어 갈 수 있습니다. 즉 다음 열로 이동을 할수있습니다. + +이 원리를 이용해서 코드를 작성 하면 아래와 같습니다. + +* c++ 코드 + +```c++ +class Solution +{ +public: + bool searchMatrix(vector>& matrix, int target) + { + //matrix[m][0]에서 탐색을 시작 하기 위한 초기화 + int m = matrix.size() -1; + int idx = 0; + + while (m >= 0 && idx target) //target보다 크다면 행이동을 합니다. + { + m--; + } + else //아닌 경우는 열 이동을 합니다. + { + idx++; + } + } + + + + return false; + } +}; +``` + + +------------------------------------------------------------ + +### 추가 문제 leetcode best time buy and sell stock + + +처음 시도해본 코드 -> 시간 초과 아마도 O(N^2)라서? + + + + +```c++ +class Solution { +public: + int maxProfit(vector& prices) + { + int max_val = -1; + int buy_idx=0; + int sell_idx = 1; + //vector temp; + //temp.reserve(100000); + + //sell val - buy val + while(buy_idx!=prices.size()-1) + { + for(int i=sell_idx;imax_val) + { + max_val = t; + + } + + } + //max_val = max(max_val,*max_element(temp.begin(),temp.end())); + + //temp.clear(); + buy_idx++; + sell_idx++; + } + + + + return max_val<=0 ? 0:max_val; + + + } +}; +``` + +시도 해볼수있는거 incre/decre로 정렬 하고 포인터 비교 하는 방식? -> 시도 하다 아닌것 같아서 포기 + +결국 solution을 보았다 2번째로 생각하던 방식이 조금 있다 결국에 최대 이익을 얻기 위해서는 최대 값 - 최소값이 유지가 되어야 한다 solution에서 확인할수있었던건 최소값유지 하는 거였다 sell - buy니까 sell이 최대값이 되고 buy가 최소값을 가지면 최대 이익이 된다 buy는 처음 부터 진행해 나가는데 (이건 1,2번째에서도 인지 했던 부분) 모든 buy값을 보는게 아니라 최소를 유지 하는 buy value만 가져가겠다는 거다. 처음 코드에서 시간 초과 나오는 이유가 buy idx를 업데이트 해나가면서 모든 sell 값을 보려고 했기 때문이다 그걸 buy idx가 업데이트 할때마다 하고 있다 그렇게 하지 말고 즉 매 buy val 마다 sell valu비교 하려 하지 말고 최소 buy val을 정하면 한번에 순회로 sell val과 비교하면서 해결 할수가 있다 + + +```c++ +class Solution { +public: + int maxProfit(vector& prices) + { + int max_val = -1; + int min_val = prices[0]; + + for(int i=1;istring문자를 추가 하는게 가능 함 요소 변경하는건 안된다 + +3. string은 heap에 할당 된다 -> string view사용하는 이유 + +4. visual studio community 2019 기준 15 character 이하면 heap에 할당 안하고 그 이상일때 할당을 한다 즉 작은 문자열에 대해 마음 편히 string 사용해도 된다 + + + +### string_views + +문자열에 대한 pointer와 길이만 가진다 -> 임시 객체를 생성하지 않고 문자열 사용할수가 있다 +문자열에 대한 사이즈 정보를 가지고 있기 때문에 null문자 없어도 종료를 알수가 있다 + + +* 다양한 문자열 타입을 전달 받을수 있는 안전하면서 효과적인 방법을 제공한다 (char 배열,c style 배열,string) + + +* 원본 데이터에 대한 변경 방지 한다 + +string view사용할때 string 타입만 사용가능한게 아니라 c style, const char등을 받을수 있으니까 1번의 할당도 하지 않기 위해 const char *를 사용할수도 있다 + + + + + + +### Intro + +안녕하세요 이번에는 문자열 파트인데요. 풀어볼 문제는 총 8문제 입니다. +posting이 길어질수 있으므로 필요한 문제만 보시는 것을 추천합니다. + + + +### 풀어볼 문제 링크 LeetCode + +[1번_implement strstr()](https://leetcode.com/problems/implement-strstr/) +{: .notice--danger} + + +[2번_Add strings ](https://leetcode.com/problems/add-strings/) +{: .notice--danger} + + +[3번_Group Anagrams](https://leetcode.com/problems/group-anagrams/) +{: .notice--danger} + + +[4번_Length of the longest substring without repeating characters](https://leetcode.com/problems/longest-substring-without-repeating-characters/) +{: .notice--danger} + +[5번_word pattern ](https://leetcode.com/problems/word-pattern/) +{: .notice--danger} + +[6번_valid paindrome I ](https://leetcode.com/problems/valid-palindrome/) +{: .notice--danger} + + +[7번_valid paindrome II](https://leetcode.com/problems/valid-palindrome-ii/) +{: .notice--danger} + + +[7번_String Matching in an Array](https://leetcode.com/problems/string-matching-in-an-array/) +{: .notice--danger} + + + + +### 추가 문제 백준 듣보잡 1764 + +```c++ + +#include +#include +#include +#include +#include + +using namespace std; + +int main() +{ + ios::sync_with_stdio(false); + cin.tie(0); + int n=0,m=0; + cin>>n; + cin>>m; + + set s; + vector result; + + for(int i=0;i>temp; + s.emplace(temp); + } + + for(int i=0;i>temp; + + // if(i==0) + // { + // continue; + // } + if(s.count(temp)==1) + { + result.emplace_back(temp); + } + + } + sort(result.begin(),result.end()); + cout< haystack.size()) + { + return -1; + } + + //needle과 현재문자열의 hash값 계산을 위한 초기화 + int needle_hash = 0; + int cur_hash = 0; + + + //초기값을 설정하는 작업이다 + for (int i = 0; i < needle.size(); i++) + { + //hash는 아스키 코드로 계산합니다 + needle_hash += needle[i] - 'a'; // needle의 hash값 계산 + cur_hash += haystack[i] - 'a'; //needle개수 만큼 haystack hash값 계산 + } + + //needle은 ll의 hash값 계산 ,haystack은 needle의 개수 2 만큼 hash 계산 즉 he까지 hash계산 + + for (int i = 0; i < haystack.size() - needle.size() + 1; i++) //needle size만큼 비교할꺼니까 + { + if (i > 0) //초기값 설정해놓았기 때문이다 + { + cur_hash += haystack[i + needle.size()-1] - 'a'; + cur_hash -= haystack[i - 1] - 'a'; + + // i ==1이었다면 cur hash 는 hel까지 hash 저장 후 -> h hash값을 지운다 즉 cur_hash = > el의 hash값이다 + } + + //hash값이 커지는것을 방지하기 위해서 값을 더 줄이기 위함이다 [소수로 나누어야함] + cur_hash %= mod; + needle_hash %= mod; + + //needle hash와 같지 않으면 다시 + if (cur_hash != needle_hash) + { + continue; + } + bool equal = true; + + //hash 충돌로 같은 값을 가질수있으므로 정말 맞는지 확인한다 + for (int j = 0; j < needle.size(); j++) //hash 충돌을 확인하는 작업이다 + { + //값이 다르면 equal =false + if (haystack[i + j] != needle[j]) + { + equal = false; + break; + } + } + //hash값이 같다면 return + if (equal) + { + return i; + } + } + + + + return -1; + + } +}; +``` + + +hash값 계산은 아스키 코드로 진행 하였고 값이 커지는 것을 방지 하기 위해 소수값인 mod로 나머지를 구한 값으로 hash값을 계산 했습니다. + + +하지만 앞서 언급 했듯이 hash를 이용하지 않고도 문제를 간단하게 풀수있습니다. 언어에서 지원하는 find 함수를 이용하면 더 쉽게 문제를 해결 할 수 있습니다. 아래는 python 코드입니다 + + +**range search로 haystack needle 문제 해결 한게 있다 참고 하기** + +```c++ +class Solution { +public: + int strStr(string haystack, string needle) + { + auto found = std::ranges::search(haystack,needle); + + if(found.empty()) + { + return -1; + } + else + { + return std::distance(haystack.begin(),found.begin()); + } + } +}; +``` + + + +leetcode가 c++20을 지원 하지 않아서 ranges를 사용할수가 없다 어쨌든 작동은 한다 + + + +* python 코드 + +```python +class Solution(object): + def strStr(self, haystack, needle): + """ + :type haystack: str + :type needle: str + :rtype: int + Input: haystack = "hello", needle = "ll" + Output: 2 + """ + return haystack.find(needle) +``` + + +------------------------------------------------------------------------------------------------------------------------------ + + +### 2 번 Add String + +문자열로 주어진 숫자를 더해서 다시 문자열로 반환하는 문제 입니다. 이 문제는 carry 즉 올림수 처리를 잘 해야 문제를 해결 할수있습니다. + +더하기를 할때 1의 자리 부터 더하므로 문자열 마지막 요소까지 index 설정하고 진행 합니다. + + +* c++ 코드 + +```c++ +class Solution +{ +public: + string addStrings(string num1, string num2) + { + + int carry = 0;//올림수 + int sum = 0,a=0,b=0; + + int size1 = num1.size() - 1; //문자열 마지막 요소로 맞추기 + int size2 = num2.size() - 1; + + string result = ""; + + //입력으로 들어온 문자열이 남아있다면 진행 합니다 + while (size1 >= 0 || size2 >= 0) + { + + a = 0; //첫 번째 문자열을 정수형으로 바꾼 값 + b = 0; //두 번째 문자열을 정수형으로 바꾼 값 + //첫 번째 문자열 길이가 남아있다면 + if (size1 >= 0) + { + + a = num1[size1] - '0'; + size1--; + } + + //두 번째 문자열 길이가 남아있다면 + if (size2 >= 0) + { + b = num2[size2] - '0'; + size2--; + } + + + //올림수 carry 까지 포함해서 더한다 + sum = (a + b)+carry; + + //sum의 올림수 계산 + carry = sum / 10; + + //result 문자열에 계산 값을 추가 합니다 + result += to_string(sum % 10); + + + + + } + + //만약 carry가 남아 있다면 + if (carry > 0) + { + result += to_string(carry); + } + + //result를 반전 합니다 + reverse(result.begin(), result.end()); + + return result; + + + + + } +}; +``` + +올림수 carry를 다루는 것에 익숙해 진다면 어렵지 않게 해결 할 수 있는 문제 입니다. + + +------------------------------------------------------------------------------------------------------------------------------ + +### 3번 Group Anagrams + + +이번 문제는 Anagram의 그룹을 찾는 문제 입니다. + +Anagram에 대해서 모르신다면 다음 링크로 가서 참고 해보세요! + +[Anagram 개념](https://ko.wikipedia.org/wiki/%EC%96%B4%EA%B5%AC%EC%A0%84%EC%B2%A0) +{: .notice--danger} + + +이제 anagram을 어떻게 찾을지를 생각 해야하는데요. 같은 문자로 다른 단어를 만드는게 anagram이니까 +입력으로 들어오는 단어를 정렬해서 저장 한다면 쉽게 anagram을 찾을 수 있습니다. + +* c++ 코드 + +```c++ +class Solution +{ +public: + string count_alpha(string str) + { + //알파벳 순서대로 숫자를 세주고 count하는 방식의 함수이다 + string result = ""; + + char count[26] = { 0, }; + + // count배열에 문자 개수 세기 + for (auto& e : str) + { + count[e - 'a']++; + + } + for (int i = 0; i < 26; i++) + { + + result += string(count[i], i + 'a'); //문자열 개수 만큼 result에 더한다 + + } + return result; + } + + vector> groupAnagrams(vector& strs) + { + + unordered_map> map; + + + //정렬한 결과를 value에 넣는 과정 + for (auto& e : strs) + { + //같은 anagram끼리 vector에 넣는다 + map[count_alpha(e)].emplace_back(e); + } + + vector> result; + + + for (auto& ee : map) + { + //두번째 요소를 result벡터에 삽입한다 + result.emplace_back(ee.second); + } + + return result; + + + } +}; +``` + +정렬을 통해 Anagram의 특성을 이해 할 수 있는 문제 였습니다. + +------------------------------------------------------------------------------------------------------------------------------ + + +### 4번 Length of the longest substring without repeating characters + + +반복하지 않는 가장 긴 substring의 길이를 찾는 문제 입니다. 이 문제는 HashMap을 이용해서 해결 할 수 있는데요. +중복된 문자가 나오면 index를 업데이틀 할 수 있기 때문 입니다. + + +* c++ 코드 + + +```c++ +class Solution +{ +public: + int lengthOfLongestSubstring(string s) + { + + int start =0; + unordered_map seen; + int result=0; + + for(int end =0;end compare1; + unordered_map compare2; + + for (auto& ele : inp1) + { + os >> token; //공백을 제거한 문자열이 옵니다 + + //compare1 hash map에서 ele를 못찾았다면 + if (compare1.find(ele) == compare1.end()) + { + //compare1에서 없는 요소가 compare2에서 찾으면 false입니다 + //왜냐하면 a 와 dog는 pattern으로 묶어져 있기 때문에 a에서 dog가 없는데 dog에 a가 있다면 fasle입니다. + if (compare2.find(token) != compare2.end()) + { + return false; + } + else //compare1,compare2에 token과 ele를 추가 합니다 + { + compare1.emplace(ele, token); + compare2.emplace(token, ele); + + } + } + //또한 compare1과 대응하는 값이 token이 아니면 false입니다. + else if (compare1[ele]!=token) + { + return false; + } + + + } + return true; +} +``` + +Hash Map 2개를 만들어서 해결 해야 한다는 점과 a와 dog는 서로 대응한다는 점을 주의 해야 했던 문제 였습니다. + + + +------------------------------------------------------------------------------------------------------------------------------ + + +### 6번 Valid Palindrome I + + +문자열 문제중 가장 흔하고 기초적인 문제로 볼 수 있습니다. + +회문은 raar 같이 시작과 끝 지점이 같은 것을 의미 합니다. + +회문이 무엇인지 모르겠다면 아래 링크를 참고 해주세요 + +[회문 위키백과](https://ko.wikipedia.org/wiki/%ED%9A%8C%EB%AC%B8) +{: .notice--danger} + + + +이 문제에서는 공백과 특수 문자등이 포함된 문자열에서 palindrome을 찾는 문제 입니다. + + +* c++ 코드 + + +```c++ + +class Solution +{ + +public: + bool isPalindrome(string s) + { + + int start = 0; + int end = s.size() - 1; + + while (start < end) + { + //공백이나 특수 문자가 아닐때까지 start와 end index를 조절 합니다 + while (start!=s.size()-1&&!isalnum(s[start])) + { + start++; + } + while (end!=0&&!isalnum(s[end])) + { + end--; + } + + //대문자라면 소문자로 바꾸는 코드 입니다 + if (isupper(s[start])) + { + s[start] += 32; + } + if (isupper(s[end])) + { + s[end] += 32; + } + + //start와 end index값이 모두 문자이고 서로 같지 않다면 + + //입력 문자열이 ",."만 올수도 있기 때문에 isalnum으로 확인하는 조건이 필요하다 + + if (isalnum(s[start])&& isalnum(s[end])&&s[start] != s[end]) + { + return false; + } + + start++; + end--; + + + } + return true; + } +}; +``` + +특수 문자나 공백을 제거 하고 start와 end index요소가 같은지 아닌지 판단 해주면 됩니다. +또한 입력 문자열이 ",." 만 올수도 있기 때문에 isalnum함수로 확인하는 조건이 필요했습니다. + + + +------------------------------------------------------------------------------------------------------------------------------ + + +### 7번 Valid Palindrome II + +이 문제는 앞에 6번 문제의 연장선의 문제 입니다. 하나만 삭제 했을때도 palindrome을 유지하는 확인하는 문제 였습니다. + +start와 end index에서 살펴 보다가 서로 다른 문자가 왔을때 (즉 palindrome 이 아닌 조건이 왔다면) +하나를 삭제 했을때도 palindrome인지 아닌지를 따져야 합니다. 크게 두가지로 생각 할 수 있습니다. + +**palindrome 따지는 두 가지** +1. start +1 부터 end까지 palindrome이 되는지 확인하기 +2. start부터 end-1까지 palindrome이 되는지 확인하기 + +앞에 2가지중 하나라도 만족 한다면 한 문자를 삭제해도 palindrome을 만족하는 문자열임을 알 수 있습니다. + + +* c++ 코드 +```c++ +class Solution +{ +public: + bool validPalindrome(string s) + { + //한개의 문자는 무조건 palindrome이다 + if (s.size() == 1) + { + return true; + } + + //index초기화 + int start = 0; + int end = s.size()-1; + + //2가지 중 하나가 맞는지 아닌지 확인하는 bool 변수 + bool result = true,result2=true; + + + while (start < end) + { + //palindrome 이 아닌 조건이 나왔다면 + if (s[start] != s[end]) + { + + //1번 따지기 + if (s[start + 1] == s[end]) + { + int l = start + 1; + int e = end; + while (l < e) + { + if (s[l] != s[e]) + { + result = false; + } + l++; + e--; + } + //palindrome이라면 true 반환하기 + if (result) + { + return true; + } + } + + //2번 따지기 + if (s[start] == s[end - 1]) + { + int l = start; + int e = end-1; + while (l < e) + { + if (s[l] != s[e]) + { + result2 = false; + } + l++; + e--; + } + //palindrome이라면 true 반환 + if (result2) + { + return true; + } + + } + //1번 2번 모두 아니라면 palindrome이 아니다 + return false; + + } + start++; + end--; + } + return true; + + + } +}; +``` + +------------------------------------------------------------------------------------------------------------------------------ + + +### 8번 String Matching in an Array + +이번 문제는 string배열에서 substring을 출력 하는 문제 였습니다. + +저는 이 문제를 풀때 대상을 제외한 모든 문자들을 비교하면서 substring인지 확인했습니다. + +* c++ 코드 + +```c++ +class Solution +{ +public: + vector stringMatching(vector& words) + { + vector result; + + for (auto& e : words) + { + //자신을 제외한 모든 문자들을 비교하기 위해서 입니다. + for (auto& ee : words) + { + //만약 자기 자신이라면 넘어 갑니다. + if (ee == e) + { + continue; + } + + //핵심 조건 + //substring임을 찾고 result벡터요소와 중복되지 않다면 + if (e.find(ee) != string::npos && find(result.begin(), result.end(), ee) == result.end()) + { + //result벡터에 추가 합니다 + result.emplace_back(ee); + + } + } + } + return result; + } +}; +``` + + +c++ string은 find함수를 제공 합니다 -> substring 확인 +그리고 algorithm 헤더 안에 find함수를 통해 vector안에 중복되는 값이 있는지 확인 할수 있었습니다. + + +------------------------------------------------------------------------------------------------------------------------------ + +### 마무리 + +string 문제는 Hash , HashMap을 자주 이용하는 것 같습니다. +혹시 아직 Hash, HashMap에 대해서 잘 모른다면 공부 하고 오신 후에 string을 보는 것을 추천 합니다. + diff --git a/_posts/2022-01-31-sort.md b/_posts/2022-01-31-sort.md new file mode 100644 index 000000000000..a7be6ef98dd4 --- /dev/null +++ b/_posts/2022-01-31-sort.md @@ -0,0 +1,903 @@ +--- +layout: single +title: "정렬 종류 개념" +categories: Sort +tag : [c++,python,Algorithm,LeetCode,Sort,Buble sort,Merge sort,Radix sort,Count sort,Shaker sort, Quick sort] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +### Intro + +이번 포스팅에서는 정렬 관련 문제를 풀기 보다 정렬 관련 종류와 개념에 대해서 포스팅 하겠습니다. + +목록을 보시면 포스팅 하려는 정렬 종류가 나와 있으니 참고 하시면 좋을 것 같습니다. + +---------------------------------------------------------------------------------------------------------------------- + +### Buble sort + +1. 큰값 또는 작은 값을 설정해서 하나하나씩 정해나가는 정렬입니다. + +2. stable한 sortring + +3. Worst Time : O(n^2) + + +* c++코드 + +```c++ +void buble_sort(int *arr,int size) +{ + for (int i = 0; i < size-1; i++) + { + for (int j = 0; j < size-1; j++) + { + if (arr[j] > arr[j+1]) + { + swap(arr[j], arr[j+1]); + } + } + } + + + //정렬한 결과를 출력하는 반복문 + for (int i = 0;i < size; i++) + { + cout << arr[i] << " "; + } + +} +``` + +* python 코드 + +```python +def Buble_sort(n): + # 뒤에서부터 정렬해 나가는 방식 + + for i in range(len(n)-1): + for j in range(len(n)-1): + if n[j] > n[j+1]: + n[j],n[j+1]=n[j+1],n[j] + + return n +``` + +------------------------------------------------------------------------------------------------------------------------ + +### Shaker sort + +Buble sort 방식을 최적화 한 정렬 입니다. 양쪽으로 buble sort하는 정렬 방식 입니다. + +양쪽으로 정렬을 진행 합니다. + +[shaker sort 위키 백과 링크](https://ko.wikipedia.org/wiki/%EC%B9%B5%ED%85%8C%EC%9D%BC_%EC%A0%95%EB%A0%AC) +{: .notice--danger} + + + +* c++ 코드 + +```c++ +void shaker_sort(int *arr,int size) +{ + int left = 0,last=0; + int right = size-1; + + while (left arr[i + 1]) + { + swap(arr[i], arr[i + 1]); + last = i; + } + } + + //마지막 요소를 정했으니 그 전의 index last를 right에 삽입 + //위의 shaker sort 위키 백과 링크 보기 + right = last; + + //역방향에서 다시 buble sort진행 + for (int j = right; j > left; j--) + { + if (arr[j] < arr[j - 1]) + { + swap(arr[j], arr[j - 1]); + last = j; + + } + + } + + left = last; + } + + for (int i = 0; i < size; i++) + { + cout << arr[i] << " "; + } +} +``` + + +* python 코드 + +```python +def shaker_sort(n): + left =0 + last =0 + right = len(n)-1 + while leftn[i+1]: + n[i],n[i+1]=n[i+1],n[i] + last = i + + #right가 range안에서 바뀌면 안되니까 last 변수 쓴것이다 + right = last + + for j in range(right,left,-1): + if n[j] 나뉠 대상이 좁혀 지는 효과가 있다. + + + +* pivot 선택 기준을 재귀로 구현하기 c++ + +```c++ +//앞,가운데,끝 요소를 정렬하는 함수 +int sorted(vector& arr,int start,int mid,int end) +{ + //세개의 index에 있는 원소를 정렬하는 함수이다 + + //처음 첫 요소 start + //가운데 인덱스 mid + //끝 인덱스 end + + //start요소가 mid 원소 보다 크면 swap한다 + if (arr[mid] < arr[start]) + { + swap(arr[mid], arr[start]); + } + + //mid 원소가 end원소보다 크면 swap한다 + if (arr[mid] > arr[end]) + { + swap(arr[mid], arr[end]); + + } + + //마지막으로 start원소가 mid보다 크면 swap한다 + if (arr[start] > arr[mid]) + { + swap(arr[start], arr[mid]); + } + + //가운데 index를 반환한다 + return mid; +} +void quick_sort_recur(vector &arr,int left,int right) +{ + //left -> 배열 처음 index + //right -> 배열 마지막 index + //원소가 2개라면 비교하고 return 한다 + if ((right - left) == 1) + { + + if (arr[right] < arr[left]) + { + + swap(arr[right], arr[left]); + } + return; + } + + //left와 right를 pl pr에 삽입한다 + int pl = left; + int pr = right; + + //sorted함수를 통해 앞,가운데,끝 인덱스를 정렬한다 + int pivot_idx = sorted(arr, left, (left + right) / 2, right); + + //pivot에 정렬된 가운데 index원소를 삽입한다 + int pivot = arr[pivot_idx]; + + + //가운데 원소와 끝에서 2번째 원소를 swap한다 + swap(arr[pivot_idx], arr[right - 1]); + + //pivot값은 끝에서 2번째 index에 위치하고 있다 + + //앞,가운데,끝은 정렬 되어있으므로 index위치 조정한다 + pl++; + pr -= 2; + + //pivot 기준으로 정렬한다 + while (pl <= pr) + { + //pivot보다 값이 작으면 pl인덱스 조정한다 -> 넘어간다는 의미 + while (pivot > arr[pl]) + { + pl++; + } + //pivot보다 값이 크면 pr인덱스 조정한다 + while (pivot < arr[pr]) + { + pr--; + } + if (pl <= pr) + { + //pivot에 맞게 swap한다 + swap(arr[pl], arr[pr]); + pl++; + pr--; + } + } + + //다시 left ~pr 까지 재귀함수로 보낸다 + if (left < pr) + { + quick_sort_recur(arr, left, pr); + } + + //pl ~ right까지 재귀함수로 보낸다 + if (right > pl) + { + quick_sort_recur(arr, pl, right); + } + +} +``` + + + +재귀 함수 부분에서는 pivot을 선택하고 pivot 기준으로 정렬을 계속 해주는 방식으로 진행 합니다. + + + + + +* stack을 이용해서 반복문으로 구현하기 c++ + +```c++ +void quick_sort_iter(vector& arr,int left,int right) +{ + //stack 자료구조 + stack> stack; + + //(left,right)범위를 stack에 넣는다 + stack.emplace(make_pair(left, right)); + + //stack이 빌때 까지 반복한다 + while (!stack.empty()) + { + //stack top에 있는 인덱스를 pl,pr인덱스에 삽입한다 + int pl = left = stack.top().first; + int pr = right = stack.top().second; + stack.pop(); + + //pivot선택 기준에 따라 정렬한다 + int pivot_idx = sorted(arr,pl,(pl+pr)/2,pr); + //가운데 index를 pivot 설정 + int pivot = arr[pivot_idx]; + + //끝 2번째와 pivot swap한다 + swap(arr[pivot_idx], arr[pr - 1]); + + //위치 조정한다 + pl++; + pr -= 2; + + + while (pl <= pr) + { + while (arr[pl] < pivot) + { + pl++; + } + while (arr[pr] > pivot) + { + pr--; + } + if (pl <= pr) + { + swap(arr[pr], arr[pl]); + pl++; + pr--; + } + + } + + //나눌 다음 대상을 stack에 push한다 + if (left < pr) + { + stack.emplace(make_pair(left,pr)); + } + if (right > pl) + { + stack.emplace(make_pair(pl, right)); + } + } + + +} +``` + + +stack구조를 사용해서 quick sort를 구현 했습니다. 재귀 함수 처럼 다음 분할 대상을 재귀함수로 보내는 것이 아니라 stack에 push하는 방식으로 진행 하였습니다. + +정리 하자면 재귀로 인덱스를 다시 보내는 대신 stack에 push함으로써 다시 분할 할수 있도록 구현 한것입니다. + +stack을 사용한 이유는 FILO(First in Last out)의 성질 때문입니다. 분할 해야하는 다음 인덱스를 먼저 처리 해야하기 때문에 stack을 사용했습니다. + + + + +* Quick sort 재귀, 반복 python 코드 +```python + +from typing import MutableSequence + +""" +Quick sort 구현 할것이다 + +pivot 선택에 따라 성능이 달라진다 + +최적의 pivot선택 방법 노트에 정리 해놨다 그 방법으로 재귀 반복 quick sort구현 할것이다 + +반드시 원소가 2개인 경우 고려 해주어야한다 + + +재귀 + +반복 +-> 스택 사용하기 +""" + +def quick_sort_recur(a:MutableSequence,left:int,right:int)->None: + + if (right-left)==1: + a[left],a[right] = sorted((a[left],a[right])) + return + pl = left + pr = right + a[pl],a[(pl+pr)//2],a[pr]=sorted((a[pl],a[(pl+pr)//2],a[pr])) + + pivot =a[(pl+pr)//2] + a[(pl + pr) // 2],a[pr-1]=a[pr-1],a[(pl+pr)//2] + + + pl+=1 + pr-=2 + + while pl<=pr: + while pivot>a[pl]: + pl+=1 + while pivotpl: + quick_sort_recur(a,pl,right) + + + +def quick_sort_iterator(a:MutableSequence,left:int,right:int)->None: + + stack=[] + stack.append((left,right)) + + while stack: + pl,pr = left,right=stack.pop() + + a[pl],a[(pl+pr)//2],a[pr] = sorted((a[pl],a[(pr+pl)//2],a[pr])) + pivot = a[(pl+pr)//2] + a[(pl + pr) // 2],a[pr-1]=a[pr-1],a[(pl+pr)//2] + pl+=1 + pr-=2 + + while pl<=pr: + while pivot>a[pl]: + pl+=1 + while pivotpl: + stack.append((pl,right)) + + + + +a = [7,8,9,6,5,4,1,2,3,3] +quick_sort_recur(a,0,len(a)-1) +print(a) + + +``` + + +Quick sort에서 pivot에 대한 이해를 하시면 merge sort를 더 잘 이해 할 수 있습니다. + + + +------------------------------------------------------------------------------------------------------------------------ + +### Merge Sort + +Quick sort에서 pivot에 대한 이해도가 있다면 이번에 merge sort는 쉽게 이해 할수 있을 것 입니다. + +Merge sort는 분할 해서 정렬후 다시 합치는 정렬 방식이기 때문입니다. + +그리고 Merge sort 구현 방식을 보면 Add string문제의 코드 구조와 비슷하다는것을 알수있습니다. + + +Time: O(n log n) + + +* c++ 코드 + +```c++ +vector mergeSort(vector& arr) +{ + //base case + //size 가 1이면 arr return 한다 + + if (arr.size() == 1) + { + return arr; + + } + + //mid설정 + int mid = arr.size() / 2; + + //left ~ mid 까지 + vector left_nums(arr.begin(), arr.begin() + mid); + + //mid ~ right 까지 + vector right_nums(arr.begin() + mid, arr.end()); + + //분할 해서 재귀함수 돌린다 + vector sorted_left = mergeSort(left_nums); + vector sorted_right = mergeSort(right_nums); + + //정렬한 결과를 저장하는 배열 + vector sorted_nums; + + int idx_l = 0, idx_r = 0; + + //left 와 right를 merge하는 코드 + while (idx_l < sorted_left.size() || idx_r < sorted_right.size()) + { + //left배열이 먼저 끝난 경우 남은 right배열을 sorted_nums배열에 넣는다 + if (idx_l == sorted_left.size()) + { + sorted_nums.emplace_back(sorted_right[idx_r]); + idx_r++; + continue; + } + if (idx_r == sorted_right.size()) + { + sorted_nums.emplace_back(sorted_left[idx_l]); + idx_l++; + continue; + } + + + //left배열 요소가 값이 더 크면 작은 값이 right배열 요소를 sorted_num배열에 넣는다 + if (sorted_right[idx_r] <= sorted_left[idx_l]) + { + sorted_nums.emplace_back(sorted_right[idx_r]); + idx_r++; + } + else + { + sorted_nums.emplace_back(sorted_left[idx_l]); + idx_l++; + + } + } + return sorted_nums; + + +} +``` + +* python 코드 +```python +""" +Merge sort + +분할 정복 느낌이 있다 + +왼 오 나누어서 분할해서 합치는 방식 -> 많이 봤던 패턴 방식이다 + +재귀적으로 구현하겠다 +""" +from typing import List +def mergeSort(nums:List[int])->List[int]: + + length = len(nums) + if length==1: + return nums + + mid =length/2 + + left_nums = nums[:mid] + right_nums = nums[mid:] + + sorted_left = mergeSort(nums=left_nums) + sorted_right = mergeSort(nums = right_nums) + + + + sorted_nums=[] + idx_l =0 + idx_r =0 + + while idx_l 최대값 - 최소값 만큼 배열 만드는 시간 O(k)가 소요 되기 때문이다 + + +Stable 한 sort입니다. + + + +**Count sort 진행 방식** + +1. 입력 리스트 원소의 개수를 저장하는 배열 만든다 +2. 만든 배열의 누적 합을 구한다 +3. 인덱스로 쓰기 위해 -1 한다 +4. 새로운 배열 B에 만든 배열의 인덱스 위치에 입력 배열의 원소를 삽입하면 정렬 된다 + + + +Time : O(n+alpha) + + + + +누적 합이 요소의 누적합이 아니라 요소가 몇개 있는지의 합이다 + + + + + + + + +* c++ 코드 + + +```c++ +void count_sort(vector&arr) +{ + //arr 원소의 개수를 저장하기 위한 배열이다 + vector temp(*max_element(arr.begin(),arr.end())+1,0); + + //결과 + vector result(arr.size(),0); + + //입력 배열 arr의 개수를 저장하는 배열 만든다 + for (auto& e : arr) + { + temp[e]++; + } + + //누적 합을 구하기 위한 변수 + int acc = 0; + + //개수를 저장한 배열temp를 누적합 배열로 바꾸는 코드 + for (int i = 0; i < temp.size(); i++) + { + acc += temp[i]; + + temp[i] = acc; + } + + //인덱스로 사용하기 위해 -1을 모든 원소에 한다 + for (int i = 0; i < temp.size(); i++) + { + temp[i] -= 1; + } + + //끝 요소부터 반복문 돌린다 + for (int i = arr.size()-1; i > -1; i--) + { + //누적합 배열 temp와 arr이용해서 result배열 넣는다 + result[temp[arr[i]]] = arr[i]; + + //같은 값의 원소를 중복으로 처리하지 않기 위함입니다. + temp[arr[i]] -= 1; + + } + + //result + cout << "Result is " << endl; + for (auto& e : result) + { + cout << e << " "; + } + + + +} +``` + +* python 코드 + + +```python +def counting_sort(a:MutableSequence)->MutableSequence: + + b=[0]*len(a) #result배열 + f=[0]*(max(a)+1) #누적 합 구하는 배열 + + for i in a: #a 인덱스 개수 센다 + f[i]+=1 + + acc=0 + for i in range(len(f)):#누적합 구한다 + acc+=f[i] + f[i]=acc + + + f = [i-1 for i in f] #누적합 구한 배열에 -1한다 + + for i in range(len(a)-1,-1,-1): #a 배열원소를 f배열 인덱스를 참고해서 b에 삽입한다 + b[f[a[i]]]=a[i] + + f[a[i]]-=1 + + return b + + +``` + +c++ 코드 기준으로 temp는 누적 도수 분포표를 만든것이라고 생각하면 됩니다. + +예) arr[8] = 3 + temp[3] = 5 + +이라고 해봅시다. 그러면 0~3사이는 5개가 있다는 의미입니다. + + + + +------------------------------------------------------------------------------------------------------------------------ + + +### Radix sort + + +count sort를 보완한 sort입니다. count sort는 최솟값과 최댓값 gap이 크면 비효율적인 알고리즘이 됩니다. +그래서 Radix sort가 보완 합니다. + +Radix sort는 자릿수로 정렬하는 정렬 방식 입니다. 단, 이때 정렬은 stable해야합니다. + + +예) [391,582,50,924,134,8,192] 배열이 있다고 할때 count sort를 이용하면 각 수를 세기 위해 900개 이상의 공간을 만들어야합니다 + +그래서 최솟값과 최댓값 gap이 크면 비효율적인 알고리즘이 됩니다. + + +Radix sort는 자릿수 별로 정렬하는 방식이라고 보면 됩니다. +일의 자리끼리 정렬하고 십의자리끼리 정렬하면서 진행 합니다. + + +Time : O(n+k)*w(자릿수 개수) + + + + +참고로 Radix sort는 count sort를 사용하기 때문에 앞에 count sort를 건너 뛰셨다면 보는것을 추천합니다. + + + +* c++ 코드 + + +```c++ +//Radix sort를 위한 count sort함수입니다. +vector count_sort_for_radix(vector&arr,int digit) +{ + //0-9까지의 원소만 올것이기때문에 10으로 설정했습니다. (자릿수끼리 비교하기 때문입니다.) + vector count(10, 0); + + + vector result(arr.size(), 0); + + for (auto& ele : arr) + { + //각 자릿수 digit에 맞게 나눠준다 그후 0-9값을 유지하기 위해 %10 한다 + int count_idx = (int)(ele / pow(10, digit))% 10; + + count[count_idx] ++; + + } + //count sort 와 동일 하다 + int acc = 0; + for (int i = 0; i < count.size(); i++) + { + acc += count[i]; + count[i] = acc; + } + for (int i = 0; i < count.size(); i++) + { + count[i] --; + } + for (int i = arr.size()-1; i > -1; i--) //뒤에서 부터 넣어주면 stable한 특성 살릴수가 있다 + { + result[count[arr[i]]] = arr[i]; + count[arr[i]] --; + } + return result; + +} + + +void Radix_sort(vector&arr) +{ + //arr의 최댓값을 찾습니다. + int max_val = *max_element(arr.begin(), arr.end()); + + //최댓값의 자릿수를 알아내는 변수입니다. + int digits = (int)(log10(max_val)) + 1; + + vector result(arr); + + for (int i = 0; i < digits;i++) + { + result = count_sort_for_radix(result, i); + } + + + cout << "\nResult is " << endl; + + for (auto& ele : result) + { + cout << ele << " "; + } + +} + +``` + +* python 코드 + + +```python +def Counting_sort_Digit(nums:List[int],digit:int)->List[int]: + counts=[0]*10 #10개로 고정되어있다 -> counting sort 쓰기 좋은 환경 + + for num in nums: + count_idx = (num//pow(10,digit))%10 + counts[count_idx]+=1 + + acc=0 + for count in range(len(counts)): + acc+=counts[count] + counts[count] = acc + + counts=[i-1 for i in counts] + + result = [0]*len(nums) + + for idx in range(len(nums)-1,-1,-1): + result[counts[nums[idx]]]=nums[idx] + counts[nums[idx]]-=1 + + return result + +def Radix_sort(nums:List[int])->List[int]: + + largest_nums = max(nums) + digits = int(math.log10(largest_nums))+1 #자릿수 개수를 계산한다 + + result = nums + for digit in range(digits): + result = Counting_sort_Digit(result,digit) + + return result + +``` + +각 자릿수끼리 비교를 하고 정렬하기 때문에 최솟값과 최댓값의 gap이 크더라도 효율적인 정렬을 할 수 있습니다. + + + +------------------------------------------------------------------------------------------------------------------------ + +### 마무리 + +이번 포스팅에서는 정렬 관련 문제를 푸는 대신 정렬의 종류와 개념을 코드로 구현 하는 시간을 가졌습니다. + +정렬 관련 문제는 이후 백준 문제 모음에서 업로드 할 예정이니 필요하신분은 백준 문제 모음 부분으로 가셔서 참고 하셔도 좋을것 같습니다. + +정렬 관련 개념이 아직 확실하지 않다면 유튜브 ,구글을 통해 학습하시는 것이 좋을 것 같습니다. + +긴 글 읽어 주셔서 감사합니다. diff --git a/_posts/2022-02-04-heap.md b/_posts/2022-02-04-heap.md new file mode 100644 index 000000000000..88479c369249 --- /dev/null +++ b/_posts/2022-02-04-heap.md @@ -0,0 +1,1209 @@ +--- +layout: single +title: "Heap 개념 및 관련 문제" +categories: [Heap,priority Queue] +tag : [c++,python,Algorithm,LeetCode,Heap,Kth Largest Element in an Array,Top K Frequent Elements,Merge k Sorted Lists ,Find the Kth Largest Integer in the Array,디스크 컨트롤러] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +### Intro + +안녕 하세요 이번 포스팅에서는 heap에 대해서 알아 볼 것 입니다. +heap에 대한 기본적인 개념과 코드 구현을 보고 Priority Queue에 대해서 알아볼것입니다. +그리고 관련 문제를 포스팅하는 것으로 마무리 하겠습니다. + + +--------------------------------------------------------------------------------------------------------------------------------- + +### Heap + +Heap은 완전 이진 트리 구조를 가지고 있는 자료 구조 입니다. +**부모 값이 자식 값 보다 크다**는 조건도 heap(max heap) 이고 **부모 값이 자식 값 보다 작다**는 조건도 heap(min heap)입니다. + +즉 두값의 대소 관계가 일정하면 heap이라고 볼 수 있습니다. + +완전 이진 트리 구조이기 때문에 max heap이라면 root노드가 가장 큰 값을 가지고 있을 것 입니다.(min heap은 반대) + +* 삽입 + +완전 이진 트리 구조이기 때문에 가장 왼쪽에 값을 삽입 후 부모 노드와 비교하면서 swap한다 + +Time : O(log N) + + + + +* 삭제 + +root 노드를 pop 후 가장 왼쪽에 있는 노드를 swap 한다 그리고 아래에서 비교를 하면서 값을 갱신한다. + +Time : O(log N) + + +min heap의 leaf node가 (완전 이진 트리니까 가장 왼쪽 leaf node) 반드시 max val이 아니다 그래서 min heap,max heap은 가장 작은수 큰수를 구하고 싶을때 사용해야 한다 +-> 그래서 min heap을 만들었다고 해서 그 요소가 다 내림차순으로 정렬되어있는게 아니다! +>문제를 풀다가 lowerbound와 같이 사용한적 있는데 lower,upper bound는 요소에서 동일 또는 큰값을 찾을 뿐이다 어쨌든 min heap 대신 sort를 해서 푼적이 있다 + + + + +-------------------------------------------------------------------------------------------------------------------------------- + + +### Hepify + +Heap구조를 만드는 것을 Heapify라고 생각 하면 될 것 같다. + +완전 이진 트리 구조 므로 Linked list를 이용해서 hepify할 수 도 있다. Tree구조에서는 부모 노드와 자식 노드가 있으므로 위에서 언급한 삽입 ,삭제 방법을 그대로 실행하면 작동 할것이다. + +그러나 배열로 구현 할 수도 있다. 이번 포스팅에서는 배열로 heapify를 구현 할 것 입니다. + +* 배열에서 관계 공식 + +1. 부모 + * (index - 1)/2 + +2. 왼쪽 자식 + * 2*index+1 + +3. 오른쪽 자식 + * 2*index+2 + + +배열 관계 공식을 통해 index통해 부모 노드 와 왼쪽 자식 노드, 오른쪽 자식 노드를 구분 할 수 있습니다. + +heap에서 언급한 삽입, 삭제 방식을 배열에서 똑같이 구현 하면 됩니다. + +c++에서는 heap을 제공해주는데 heap구현을 살펴 본 다음 c++에서 제공하는 heap 사용법을 간단하게 알아 보겠습니다. + + +* heap구현 c++ 코드 + +```c++ +#include + +using namespace std; + +class Heap +{ +private: + + int* arr; + int current_size; + int size_; + + //가운데 지점에서 계속 heapify 진행하기 위함이다 + void convertArrayIntoHeap() + { + //current_size/2-1 -> 부모 노드 부터 시작 + for (int i = current_size / 2 -1; i > -1; i--) + { + heapify(current_size, i); + } + } + + + void heapify(int size, int rootidx) + { + //부모 노드 + int max_val = rootidx; + + int left = 2 * rootidx + 1; //왼쪽 자식 노드 + + int right = 2 * rootidx + 2; //오른쪽 자식 노드 + + //왼쪽 자식 노드가 부모 노드보다 큰 경우 부모 노드 갱신 + if (leftarr[max_val]) + { + max_val = left; + } + + //오른쪽 자식 노드가 부모 노드보다 큰 경우 부모 노드 갱신 + if (rightarr[max_val]) + { + max_val = right; + } + + //처음 부모 노드 max_val이 바뀌었다면 swap한다 + if (max_val != rootidx) + { + swap(arr[max_val], arr[rootidx]); + heapify(size, max_val); + } + } + + +public: + + //constructor + Heap(int size) + :current_size(0),size_(size),arr(new int [size]) + { + + } + + //insertion heap + + void insert(const int value) + { + if (current_size < size_) + { + arr[current_size] = value; + current_size++; + if (current_size == size_) + { + convertArrayIntoHeap(); + } + } + else + { + cout << "\nThe heap is full.\n"; + } + } + + int top() + { + return arr[0]; + } + + int pop() + { + //root 노드와 가장 왼쪽 노드를 swap한다 + swap(arr[0], arr[current_size - 1]); + + //그후 값을 비교 갱신한다 + heapify(current_size - 1, 0); + + //값 1개가 삭제 되므로 size가 줄어든다 + current_size--; + + return arr[current_size]; + } + void print_heap() + { + for (int i = 0; i < size_; i++) + { + cout << arr[i] << ' '; + } + } + +}; + + +int main() +{ + int size = 9; + Heap heap(size); + + for (int i = 0; i < size; i++) + { + int value = 0; + cin >> value; + heap.insert(value); + } + heap.print_heap(); + + return 0; +} +``` + + +위의 코드는 class로 heap구조를 구현한 코드입니다. + +아래의 코드도 maxheap이며 구조는 거의 동일하나 max_heap 구현에 더 초점이 맞추어져 있습니다. + + + +* max_heap c++ 코드 + + + +```c++ + +void max_hepify(vector &arr,int rootidx) +{ + + int left = 2 * rootidx + 1; //왼쪽 자식 노드 + int right = 2 * rootidx + 2; //오른쪽 자식 노드 + + int Max = rootidx; //부모 노드 + + //오른쪽 자식 노드, 왼쪽 자식 노드 값이 부모 노드 보다 크면 부모노드를 갱신한다 + if (leftarr[Max]) + { + Max = left; + } + if (rightarr[Max]) + { + Max = right; + } + + //부모 노드가 처음과 다르면 + if (Max != rootidx) + { + //swap한다 + swap(arr[Max], arr[rootidx]); + + //swap한 값을 갱신한다 + max_hepify(arr, Max); //재귀 함수 호출 + } + + + +} +int main() +{ + vector arr = { 1,3,5,2,4,8,6,7,9 }; + + + for (int i = arr.size() / 2; i > -1; i--) + { + //부모 노드가 i로 들어간다 + max_hepify(arr, i); + } + + + + for (auto& e : arr) + { + cout << e << " "; + } + cout << endl; + return 0; +} +``` + + + +* c++에서 heap 사용하는 예 cppreference make_heap에서 참고한 코드 입니다. + +```c++ +/* + make_heap in cpp + + c++에서 heap 기초 사용 방법 + + #include 해야한다 + + + make_heap(first,last,comp) + + first last 는 주소 요소의 범위이다 + + comp는 comparison function object이다 sort처럼 비교함수가 올수있다 + + + + + -max heap은 + make_heap그대로 사용하면 됨 + + -min heap + make_heap(first,last,greater<>) comp함수 써야한다 + + + a new element can be added using std::push_heap(). 새로운 요소를 추가할때 사용가능 + + the first element can be removed using std::pop_heap(). + +*/ +void print(std::string_view text, std::vector const& v = {}) { + std::cout << text << ": "; + for (const auto& e : v) std::cout << e << ' '; + std::cout << '\n'; +} + +int main() +{ + print("Max heap"); + + std::vector v{ 3, 2, 4, 1, 5, 9 }; + print("initially, v", v); + + std::make_heap(v.begin(), v.end()); + print("after make_heap, v", v); + + std::pop_heap(v.begin(), v.end()); + print("after pop_heap, v", v); + + auto top = v.back(); + v.pop_back(); + print("former top element", { top }); + print("after removing the former top element, v", v); + + print("\nMin heap"); + + std::vector v1{ 3, 2, 4, 1, 5, 9 }; + print("initially, v1", v1); + + std::make_heap(v1.begin(), v1.end(), std::greater<>{}); + print("after make_heap, v1", v1); + + std::pop_heap(v1.begin(), v1.end(), std::greater<>{}); + print("after pop_heap, v1", v1); + + auto top1 = v1.back(); + + v1.pop_back(); + + print("former top element", { top1 }); + print("after removing the former top element, v1", v1); +} + + +``` + + +
+
+ +### 위에서 구현한 heapy기준으로 heap sort 만들기 + +```c++ +vector heapsort(vector&vec) +{ + vector result; + + make_heap(vec.begin(),vec.end()); + while(!vec.empty()) + { + pop_heap(vec.begin(),vec.end()); + result.emplace_back(vec.back()); + vec.pop_back(); + } + return result; + + + +} + +``` + +
+
+ + +-------------------------------------------------------------------------------------------------------------------------------- + +### Priority Queue + +우선 순위 큐는 일반적인 큐와 다른 구조의 큐 구조 입니다. 큐와 스택은 원소가 들어가는 순서 나가는 순서가 정해져 있는 자료 구조이지만 +우선 순위 큐는 우선 순위에 따라 data가 나가는 자료 구조 입니다.(**들어간 순서에 상관없이 우선순위가 높은 데이터가 먼저 나오는구조**) 우선 순위 큐는 기본 적으로 heap의 구조 이기 때문에 heap 포스팅에서 다뤄 보겠습니다. + +우선 순위 큐는 배열, linked list로 구현 할 수 도 있지만 최악의 경우 Time : O(n)이 걸립니다. +그래서 우선 순위 큐를 구현 할때 heap의 구조를 사용해서 구현 합니다. + +heap과 동일하지만 heap에서는 값이 크다 작다 즉 max, min으로 이루어져 있다면 우선 순위 큐에서는 꼭 값이 크다 작다만이 기준이 아닌것 같습니다. pair로 예를 들어보면 first가 3보다 작은 것 중에서 second가 작은것 부터 우선 순위를 따진다면 이때 우선 순위 큐를 사용하는것 같습니다. (개인적인 생각이라서 틀렸다면 댓글로 지적 해주세요) + + +c++에서 지원하는 priority Queue 사용법을 간단하게 알아보겠습니다. + + +* c++ 코드 cppreference에서 참고한 코드 입니다. + +```c++ +#include +#include +#include + +/* + STL priority queue 사용법을 간단하게 정리해봄 + + #inculde 를 해야한다 + + heap과 같다고 보면 된다 + + + template< + class T, + class Container = std::vector, + class Compare = std::less +> class priority_queue; + + +큐와 마찬가지로 front pop등 기능을 지원한다 + +*/ + +using namespace std; + +int main() +{ + priority_queue q; //기본으로 하면 max heap그리고 min heap다를수있다 자료형 크기대로 우선순위정해짐 + + vector data = { 1,8,5,6,3,4,0,9,7,2 }; + + for (auto& n : data) + { + q.push(n); + } + + cout << "This is q " << endl; + while (!q.empty()) + { + cout << q.top() << " "; + q.pop(); + } + cout << endl; + + priority_queue, greater> q2(data.begin(), data.end()); + + cout << "This is q2" << endl; + while (!q2.empty()) + { + cout << q2.top() << " "; + q2.pop(); + } + cout << endl; + + // Using lambda to compare elements. + //auto cmp = [](int left, int right) { return (left ^ 1) < (right ^ 1); }; + //std::priority_queue, decltype(cmp)> q3(cmp); + + //decltype은 auto와 비슷하지만 auto와 다르게 타입을 그대로 보존한다 + + + + return 0; +} +``` + + +
+
+ +그리고 unordered map에서 second 값 기준으로 heap을 구성하고 싶다면 다음과 같이 사용할수가 있다 + +```c++ +#include +#include +#include + +using namespace std; + + +int main() { + unordered_map map; + map[3] = 1; + map[33] = 2; + map[333] = 3; + + + auto cmp = [](pair const& p1, pair const& p2) + { + return p1.second < p2.second; + }; + + priority_queue, vector>,decltype(cmp)> pq; + + for (auto&it:map) { + pq.push(make_pair(it.first, it.second)); + } + + while (!pq.empty()) { + auto top = pq.top(); + pq.pop(); + cout << top.first << " " << top.second << endl; + } + + return 0; +} + + +``` + +이때 비교 함수(compare function)은 람다 표현식 사용해도 되지만 decltype()안에다가 선언해야 작동을 한다!->c++20기준 + +참고로 c++17로 돌린다고 하면 + +```c++ + struct Compare { + bool operator()(pair const& p1, pair const& p2) { + return p1.second < p2.second; + } + }; + +``` + +다음과 같은 구조체를 만들어야 한다 람다 표현식 그대로 인자로 넣으면 작동 하지 않는다 + +
+
+ + +compare함수에 기준 여러개 적용하기 + +문제를 따로 언급 하지 않겠지만 문제를 풀다가 여러 조건을 compare함수에 넣어야 할때가 있었는데 그 내용을 정리 해보려 한다 + +1. abs(x)+abs(y)의 값이 작은것을 기준 +2. x가 작은값을 기준 +3. y가 작은값을 기준 + +다음과 같은 3개의 기준을 compare함수에 적용한 코드이다 + +```c++ +struct Compare { + bool operator()(pair& a,pair& b) { + long long int sum1 = abs(a.first) + abs(a.second); + long long int sum2 = abs(b.first) + abs(b.second); + if (sum1 != sum2) { + return sum1 > sum2; + } + else if (a.first != b.first) { + return a.first > b.first; + } + else { + return a.second > b.second; + } + } +}; +``` + +----------------------------------------------------------------------------- + + + + + + + +기본적인 개념과 구현은 살펴 본것 같으니 관련 문제를 풀어보도록 하겠습니다. + + + + + +-------------------------------------------------------------------------------------------------------------------------------- + + +### 문제 링크 + +[1번 Kth Largest Element in an Array](https://leetcode.com/problems/kth-largest-element-in-an-array/) +{: .notice--danger} + +[2번 Top K Frequent Elements](https://leetcode.com/problems/top-k-frequent-elements/) +{: .notice--danger} + +[3번 Merge k Sorted Lists](https://leetcode.com/problems/merge-k-sorted-lists/) +{: .notice--danger} + +[4번 Find the Kth Largest Integer in the Array](https://leetcode.com/problems/find-the-kth-largest-integer-in-the-array/) +{: .notice--danger} + +[5번 디스크 컨트롤러](https://programmers.co.kr/learn/courses/30/lessons/42627) +{: .notice--danger} + +[앞에서 삭제하기 II 코드트리](https://www.codetree.ai/missions/8/problems/delete-it-from-the-beginning-2?utm_source=clipboard&utm_medium=text) + +-------------------------------------------------------------------------------------------------------------------------------- + + +### 1번 Kth Largest Element in an Array + + +k 번재로 큰 숫자를 배열에서 찾는 문제 입니다. 이 문제를 보고 그냥 정렬후 k번째 원소를 반환하면 될 것이라고 생각합니다. +그런데 min heap으로 문제를 풀면 더 효율적으로 문제를 풀 수 있습니다. + + +* min heap 방법 + + * k 크기만큼 min heap을 만들고 크기가 넘어가면 pop을 한다 그러면 K번째로 큰 요소와 k까지의 요소까지 구할수있다 + + * k크기만큼 빼고 pop한다는건 최소값들을 계속해서 pop 하기때문에 결국 남는건 점점 큰숫자들만 남게 된다 + + +삽입 삭제가 O(log n)인데 k개의 size만큼 삽입 삭제를 반복하니까 + +Time : O(n log k) + +space : O(k) + +-> space complex를 줄일 수 있다 + +* c++ 코드 + +```c++ +class Solution { +public: + int findKthLargest(vector& nums, int k) + { + // temp 벡터 + vector temp; + for(auto&e:nums) + { + temp.emplace_back(e); + + //min heap구성 한다 + push_heap(temp.begin(),temp.end(),greater<>{}); + + //size가 k개 넘어가면 + if(k{}); + temp.pop_back(); + } + } + + // k 번째로 큰 수 return 한다 + return temp[0]; + } +}; +``` + + +min heap을 이용해서 k 번째로 큰 수를 찾는 방법에 대해 알아 보았습니다. + + + +-------------------------------------------------------------------------------------------------------------------------------- + +### 2번 Top K Frequent Elements + +자주 나오는 k개의 element를 구하는 문제 입니다. + +이 문제는 + +1. hash map 과 sort를 이용해서 풀 수도 있다 + +Time: O(N+ N log N) + + +2. hash_map + min heap + +앞에 문제 처럼 min heap과 k를 이용해서 빈번의 최대를 따로 저장할수있다 ->정렬을 할필요없다 +k는 상수로 볼수있다 + +Time complextiy -> O(n+m)으로 볼수있다 + + +앞의 1번 문제와 비슷한 방식으로 풀 수 있다 + +hashmap으로 element를 count해주고 count한 value기준으로 min heap을 해주면 k개의 빈번한 element를 구할 수 있다. + + + +* c++ 코드 + + +```c++ +class Solution { +public: + vector topKFrequent(vector& nums, int k) + { + //hash map을 vector로 쓰기 위함 + vector> result; + + //hash map + unordered_map heap_hash; + + for (auto& cur : nums) + { + //중복 체크를 하는 과정없이 구현할수있다 + heap_hash[cur]++; + } + + for (auto& e : heap_hash) + { + //첫 번째 원소를 기준으로 min heap을 만드니까 e.second부터 삽입한다 count 기준으로 min heap진행 해야한다 + result.emplace_back(e.second,e.first); + push_heap(result.begin(), result.end(), greater<>{}); + + if (k < result.size()) + { + pop_heap(result.begin(), result.end(), greater<>{}); + result.pop_back(); + } + } + + //결과 : 반환 벡터 + vector Result; + + for (int i = 0; i < result.size(); i++) + { + Result.emplace_back(result[i].second); + } + + return Result; + + + } +}; +``` + + +맨위에서 priority queue와 unordered_map을 가지고 compare함수 기준으로 처리한 코드는 다음과 같다 +대부분의 코테 사이트에서 c++20을 지원하지 않기 때문에 compare함수는 람다 표현식으로 사용하지 말고 구조체나 함수 식으로 사용해야 한다 + +
+
+ + +```c++ +class Solution { +public: + + struct Compare { + bool operator()(pair const& p1, pair const& p2) { + return p1.second < p2.second; + } + }; + + vector topKFrequent(vector& nums, int k) + { + unordered_map map; + + for(auto&e:nums) + { + map[e]++; + } + + + priority_queue, vector>,Compare> pq; + for(auto&e:map) + { + pq.push(make_pair(e.first,e.second)); + } + + + vector result; + + for(int i=0;i& lists) + { + //리스트 사이즈가 0이면 + if (lists.size() == 0) + { + return NULL; + } + + //ListNode * ret, tail NULL 초기화 + ListNode* ret = NULL; + ListNode* tail = NULL; + + + + //priority 에서 lamda 표현식 사용하는 방법 (min hepa 만들기 위함 ) + auto comp = [](ListNode* a, ListNode* b) {return a->val > b->val; }; + + + //우선 순위 큐 + priority_queue, decltype(comp)> pq(comp); + + + //list에 원소 있다면 모두 우선순위 큐에 넣는다 + for (int i=0;inext; + + } + + } + + //우선 순위 큐에는 각 리스트 노드의 벡터 값이 우선 순위에 따라 정렬 되어있음 + + while (!pq.empty()) + { + + + ListNode* temp = pq.top(); + pq.pop(); + + if (temp->next != NULL) + { + pq.push(temp->next); + } + + //List add 과정 + + if (ret == NULL) + { + ret = temp; + } + else + { + tail->next = temp; + } + tail = temp; + } + + return ret; + } +}; +``` + + +예) [[1,5,4],[1,4,3],[6,2]] + +우선 순위 큐에 넣으면 + +[[1,4,5],[1,3,4],[2,6]]의 순서대로 바뀝니다 + +그리고 while(!pq.empty())까지 첫 요소부터 비교를 하면서 linked list를 만듭니다. + + +정렬 포스팅에서는 다루지 않았지만 정렬중에 heap을 이용한 정렬 heap 정렬이 있습니다. +heap정렬에 대해 공부 하시면 위의 코드를 더 쉽게 이해 할 수 있을 것 입니다. + + +-------------------------------------------------------------------------------------------------------------------------------- + + +### 4번 Kth Largest Element in an Array + + +이번 문제는 k번째 요소를 구하는게 아니라 정렬을 했을때 k번째로 큰 수를 구하는 문제 입니다. + +주의 하실 점은 +**Note: Duplicate numbers should be counted distinctly. For example, if nums is ["1","2","2"], "2" is the first largest integer, "2" is the second-largest integer, and "1" is the third-largest integer.** + + +중복된 숫자도 별개로 숫자를 세주어야한다는 것입니다. + + + +1번 문제와 비슷한 유형의 문제 입니다. 단 배열 내용이 string인 점 그리고 중복된 원소는 따로 count해야한다는 점을 인지 하고 문제를 풀 면 되겠습니다. + + + +* c++ 코드 + + +```c++ +class Solution +{ +public: + +//자릿수로 크기 정하는 compator구조체 + struct compator { + bool operator()(string a, string b) + { + if (a.size() == b.size()) + { + for (int i = 0; i < a.size(); i++) + { + if (a[i] - '0' > b[i] - '0') + { + return true; + } + else if (a[i] - '0' < b[i] - '0') + { + return false; + } + } + } + return a.size() > b.size(); + } + }; + + string kthLargestNumber(vector& nums, int k) + { + priority_queue,compator> q; + + //1번 문제의 min heap과 비슷한 구조 + for (auto& e : nums) + { + q.push(e); + if (k < q.size()) + { + q.pop(); + } + } + return q.top(); + } +}; +``` + + +-------------------------------------------------------------------------------------------------------------------------------- + + +### 5번 디스크 컨트롤러 + +이번 문제는 Leetcode가 아니라 프로그래머스에 있는 문제 입니다. + +개인적으로 이번 문제에서 아쉬웠던 점은 같은 시간대에서는 우선 순위 큐 순서에 따라(1번째 원소를 기준으로) 처리를 하였는데 다른 작업 시간대에 대해서 처리를 잘 하지 못해서 해맸던 문제 였습니다. + + +* c++ 코드 + + +```c++ + +#include +#include +#include +#include +#include + +using namespace std; + +//1번째 원소를 비교하여 우선순위 정한다 +struct compater +{ + bool operator()(vector a,vector b) + { + + return a[1] > b[1]; + } +}; +int solution(vector> jobs) +{ + int answer = 0; + sort(jobs.begin(), jobs.end()); + + //우선 순위 큐 + priority_queue, vector>, compater> q; + + int temp = 0, j = 0; + while (j < jobs.size() || !q.empty()) + { + + //같은 시간대에 다른 작업들 있으면 우선 순위 큐에 넣어준다 + if (jobs.size() > j && temp >= jobs[j][0]) + { + q.push(jobs[j]); + j++; + continue; + } + + if (!q.empty()) + { + //0번째 와 1번째의 차이를 구하는 코드 + temp += q.top()[1]; + answer += temp - q.top()[0]; + + q.pop(); + } + else //다음 작업으로 진행 + { + //다른 작업 시간이라면 temp를 새로 지정해야한다 + //같은 시간대의 시간대만 따로 처리 하기 위함이다 + + temp = jobs[j][0]; + } + } + return answer / jobs.size(); +} + +``` + +다른 작업 시간대의 처리만 잘 했다면 우선 순위 큐를 이용해서 풀기 좋은 문제 였습니다. + +-------------------------------------------------------------------------------------------------------------------------------- + +### 앞에서 부터 삭제 하기 - 코드 트리 문제 + + +얼핏 보면 쉬운 문제 처럼 보이지만 처음 풀어본 입장에서 쉬운 문제는 아니었다 + +앞에서 부터 k개를 삭제 하고 난 후 남아있는 숫자에서 가장 작은 숫자 하나를 구해야 하니까 min heap을 사용해야 한다는것을 알수가 있었다 그리고 나서 평균 구하는건 할수가 있으니 넘어가는데.. 문제는 k개를 지웠을때 평균값이 최대가 되어야 한다는것을 어떤 방식으로 전개하면 알수있을지 처음에 고민이 되었다 k는 1부터 n-2까지만 고려 한다고 했으므로 고려 끝에 1~n-2까지 k개를 지우면서 고려 해야 하겠다고 생각 했다 그래서 1~n-2까지 삭제 하고 min heap만들고 처리 했는데 시간 초과가 나와서 질문을 해본 결과 min heap은 o(n)이 걸리는데 이걸 for 문 안에서 돌리고 있어서 시간초과가 나오는것을 알게 되었다 이때 k를 n-2부터 1까지 고려를 해보면 min heap의 사용 빈도를 줄일수있을것이라는 힌트를 얻게 되었는데 이 힌트를 적용하면 처음에만 min heap을 사용하고 이후에는 push heap , pop heap만으로도 처리가 가능하다는것을 알게 되었다 (log(n)) 하지만 여전히 시간 초과가 나와서 정말 당황을 했는데 accumulate에서 발생하는 시간 초과임을 알수있었다 이걸 해결하기 위해 다른 사람들이 푼 방식을 보았는데 누적 합으로 해결하고 있었다 사실 다른 내용 보다 이 내용 때문에 지금까지 길게 정리하는것도 있다 이후 내용에도 누적합(prefix sum)에 대해 내용 정리 할것인데 [개념은 거기서 보기] 우선 여기 문제만 생각 해본다 할때 상황이 딱 맞는 상황임을 뒤늦게 알수있었다 + +
+
+ +내가 작성한 코드 + + +```c++ +#include +#include +#include +#include +#include +//using namespace std; +int main() +{ + std::ios::sync_with_stdio(false); + std::cin.tie(NULL); + + std::vectorv; + std::vectorprefix_sum; + v.reserve(100000); + prefix_sum.reserve(100000); + + int n=0; + double max_val = -1.0; + prefix_sum.emplace_back(0); + //std::cout<<"DDD "; + std::cin>>n; + + + for(int i=1;i<=n;i++) + { + + int t=0; + std::cin>>t; + + v.emplace_back(t); + prefix_sum[i] = prefix_sum[i-1]+t; + + + } + + std::vector temp(v.begin()+(n-3), v.end()); + std::make_heap(temp.begin(), temp.end(), std::greater<>{}); + + + for(int i=n-3;i>=0;i--) + { + //std::vector temp = v; + //std::make_heap(temp.begin()+i, temp.end(), std::greater<>{}); + if(i!=n-3) + { + + temp.emplace_back(v[i]); + + push_heap(temp.begin(),temp.end(),std::greater<>{}); + } + + std::pop_heap(temp.begin(), temp.end(), std::greater<>{}); + + int ele = temp.back(); + //std::cout<<"CCC "<{}); + + + + + + } + + std::cout.precision(2); + std::cout << std::fixed; + std::cout< arr = { "","", "abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"}; + + //입력 받은 숫자 + string num = ""; + + //결과 반환하는 벡터 배열 + vector result; +public: + + // 1.recursion할때 마다 무엇을 선택할 것인가 + //str,idx를 재귀함수 할때마다 고려할 것이다 + void BT(string str, int idx) + { + //num의 사이즈와 같을때까지 반복한다 + //Base case + if (str.size() == num.size()) + { + result.emplace_back(str); + return; + } + + + for (auto& e : arr[num[idx] - '0']) + { + //다음 재귀함수 프로세스와 현재 프로세스 종료 설정 + BT(str+e, idx + 1); + } + } + + vector letterCombinations(string digits) + { + //빈 문자이면 빈 배열 반환 -> 예외 상황 처리 + if (digits == "") + { + return {}; + } + + //입력 받은 숫자를 num에 넣는다 + num = digits; + BT("", 0); + + return result; + + } +}; +``` + + + + +----------------------------------------------------------------------------------------------------------------------------------------------------- + + +### 2번 Backtracking subsets + + +이번 문제는 중복이 없는 subset을 구하는 문제 입니다. 각 문자를 뽑는 경우 아닌경우를 나누어서 생각하면 됩니다. + + +* c++ 코드 + +```c++ +class Solution { + vector> result; + vector num; +public: + + //idx, letter를 매개 변수로 넣으면서 진행 + void BT(int idx, vector& letter) + { + //base case 종료 조건 + if (idx == num.size()) + { + result.emplace_back(letter); + return; + } + + + //해당 문자 안 뽑는 경우 + //다음 재귀함수 프로세스 설정 + BT(idx + 1, letter); + + letter.emplace_back(num[idx]); //해당 문자를 추가한다 + + //해당 문자 뽑는 경우 + //현재 프로세스 종료 설정 + BT(idx + 1, letter); + letter.pop_back(); //원상 복귀 + + } + + vector> subsets(vector& nums) + { + num = nums; + vector letter; + + + BT(0, letter); + + return result; + + } +}; +``` + + +이번 문제는 문자를 뽑고 안뽑고 진행하는 모델을 먼저 생각하고 backtracking으로 진행하는게 중요한 문제 였습니다. + +Time : O(n 2^n) + + * 진행 될때마다 2배씩 늘어나기 때문에 2^n이고 이 과정이 n번 수행 되기 때문입니다. + + +Space : O(n) + + +----------------------------------------------------------------------------------------------------------------------------------------------------- + +### 3번 permutations + + +이번 문제는 순열 문제 입니다. 결과 값의 개수는 n!만큼 나올 것입니다. + + +왜 n!의 개수가 나오는지 생각해보면 이해하기 쉬워질 것입니다. + +1 2 3 4가 있다면 처음 경우만 생각 해보겠습니다. + +1 2 3 4 +2 1 3 4 (1과 2를 swap한 결과) +3 1 2 4 (2와 3을 swap한 결과) +4 1 2 3 (3와 4을 swap한 결과)이 가능 할것입니다. (아래 for반복문이 swap하는 이유 ) + +각 반복문의 BT함수는 1 2 3 4일때 2 1 3 4일때를 차례대로 들어가면서 가능한 순열의 경우를 탐색해 나갑니다. + +(1 2 3 4는 또 다시 여러개의 순열로 이루어질수있습니다. 2를 대상으로 다시 swap합니다.) + + + + +* c++ 코드 + +```c++ +class Solution { + vector> ans; +public: + void BT(int idx, vector nums) + { + //base case + if (idx == nums.size()) + { + ans.emplace_back(nums); + return; + } + + //idx부터 swap하는 반복문 + //현재 프로세스 종료 설정 + for (int i = idx; i < nums.size(); i++) + { + //다음 재귀함수 프로세스 설정 + swap(nums[idx], nums[i]); + BT(idx + 1, nums); + + } + } + vector> permute(vector& nums) + { + BT(0, nums); + + return ans; + } +}; + +``` + +----------------------------------------------------------------------------------------------------------------------------------------------------- + +### 4번 Combination Sum + + + +이번 문제에서 고려할 점은 후보 배열의 원소가 반복해서 사용될수있다는 것입니다. + + +* c++ 코드 + +```c++ +class Solution { + vector> result; + vector num; +public: + + void BT(int idx, int target, vector arr) + { + //base case 종료 조건 + if (target == 0) + { + result.emplace_back(arr); + } + else if (target < 0) //target==0되고 한번 더 들어오면 종료한다 + //back하는 조건 + { + return; + } + /* + 입력 배열 원소가 반복될수있기 때문에 반복문 설정을 잘 해주어야하는데 + 현재 원소 인덱스 부터 시작할수있게 idx설정한다 + + */ + for (int i = idx; i < num.size(); i++) + { + arr.emplace_back(num[i]); + + BT(i, target - num[i], arr); //다음 재귀함수 프로세스 설정 i번째부터 시작하기 위해 idx = i , + //target-num[i]해서 target탐색 + //현재 프로세스 종료 설정 + arr.pop_back(); + } + return; + + } + vector> combinationSum(vector& candidates, int target) + { + num = candidates; + vector arr; + BT(0, target, arr); + + return result; + } +}; +``` + + + +사실 앞에 3문제는 backtracking문제라기 보다 Recursive, DFS문제에 가깝다고 생각합니다. +거의 모든 경우를 탐색하기 때문입니다. 하지만 이번 문제는 그나마 조건을 보고 아니면 back하는 프로세스를 조금 +느끼실 수 있던 문제였다고 생각 합니다. + + +----------------------------------------------------------------------------------------------------------------------------------------------------- + +### 5번 restore_ip_adr + + +문제 이해가 안될수도 있기 때문에 추가 설명을 먼저 하겠습니다. + + +ip version 4의 ip Format은 다음과 같습니다. (8bit를 4개 쓸수있다는 의미와 동일 합니다. 8bit의 범위는 0~255) + + +IP Format : (0-255).(0-255).(0-255).(0-255) + +문제에서 invalid ip가 왜 안되는지 이해 했을 것 이라고 생각 됩니다. + + +invalid한 ip 조건 + +1. leading 0오면 안된다 +2. ip format을 넘어가는 숫자 오면 안된다 즉 255이상 0이하의 숫자가 오면 안됩니다. + + + +가능성을 파악하는 문제 => Backtracking 문제 입니다. + + + **BASE CASE** + + 1. Catch answer + + -> vector에 추가를 해야한다 그러려면 일단 string s끝까지 탐색이 되어야한다 + 그리고 . 기준 정수 개수가 정확히 4개 여야한다 그래야 ip version 4이다 + + 2. Dead Path + + 2-1 ) . 기준으로 정수가 4개 이상인 경우는 더 안봐도 된다 + 2-2) 비교 숫자가 0-255범위를 벗어난다면 더 안봐도 된다 + + +**Process** + +1. Choose + + 0-255 범위를 확인을 해야한다 그러면 1자리부터 100자리까지 모든 가능성을 확인해야한다 + +2. Explore + + 0-255 범위가 맞다면 추가 하면서 recursive 로 탐색해나간다 + +3. Unchoose + + 3-1) 0-255 범위를 벗어난다면 선택하지 않고 다음으로 넘어간다 + 3-2) leading 0가 있다면 더이상 가지 않고 다음으로 넘어간다 + + + +* c++ 코드 + +```c++ +class Solution +{ + vector res; + +public: + + void BT(string t, int counter, string& s, int pos) + { + // counter 는 . 기준으로 정수가 4개를 넘어가는지 아닌지 확인하는 변수 + //pos는 대상 s끝까지 갔는지 확인하는 변수 + + //BASE CASE in BT + + if (pos == s.size() && counter == 4) + { + t.pop_back(); //t 마지막에 . 을 제거하기 위함이다 + res.emplace_back(t); + return; + } + + // .기준으로 정수가 4개 넘어갔을때 DEAD PATH + if (counter > 4) + { + return; + } + + for (int start = 1; start <= 3 && pos + start <= s.size(); start++) + { + string temp = s.substr(pos, start); + stringstream os; + os.clear(); + + //Unchoose in BT leading 0를 선택하지 않는다 + if (temp.size() > 1 && temp[0] == '0') + { + continue; + } + + //문자열-> 정수 변환 + os.str(temp); + int val = 0; + os >> val; + + //Choose in BT + if (val <= 255 && val >= 0) + { + string kl = t + temp+'.'; //1자리부터 . 붙히면서 모든 가능성을 본다 + //왜냐하면 정확히 4개의 정수로 나누어 떨어져야하기때문이다 그리고 0-255범위이기때문에 start는 1부터 3인것이다 + + + //pos+start 는 다음으로 넘어간다는 의미이다 + BT(kl, counter + 1, s, pos + start); + + } + + + + } + + + } + + vector restoreIpAddresses(string s) + { + BT("", 0, s, 0); + return res; + } +}; + +``` + + +이번 문제를 통해 backtracking을 좀더 이해 할수있었을것이라고 생각 됩니다. +모든 가능성 중에서 원하는 조건일때만 탐색을 진행하고 아니라면 빠져나가는 가지치기를 경험 할 수 있었습니다. + + + +----------------------------------------------------------------------------------------------------------------------------------------------------- + +### 6번 Word Search + + +이번 문제는 word search 문제 입니다. grid에서 해당 word가 있으면 T아니라면 F를 반환하는 문제 입니다. +한 문자 기준으로 위 아래 왼쪽 오른쪽 4방향으로 이동할수있습니다. + + +해당 단어가 있는지 모든 경우를 우선 탐색해야합니다. 탐색 과정 속에서 조건이 맞지 않으면 back을 해야 하는 문제이므로 backtracking문제입니다. + + +문제 전략은 스타트 지점에서 4방향을 모두 탐색을 이어갈것입니다. 4방향중에서 하나라도 true가 나온다면 해당 word가 존재한다는 의미이므로 true를 반환 할 것입니다. + + +스타터 지점을 찾고 탐색을 이어가는게 핵심입니다. + + + +* c++ 코드 + +```c++ +class Solution +{ + string temp; + +public: + + bool BT(vector>& board,int idx,int row,int col) + { + //base case + + //size에 도달하면 True + if (idx==temp.size()) + { + + return true; + } + + //back 조건 + //board를 벗어나는 경우 그리고 다음 row col이 temp[idx]가 아니라면 false를 반환한다 + if (row < 0 || col < 0 || row >= board.size() || col >= board[0].size() || board[row][col] != temp[idx]) + { + + return false; + } + + + + board[row][col] = ' '; + + //다음으로 탐색 하는 프로세스 설정 + //4방향에 대해서 모든 경우에 T/F를 or개념으로 묶는다 하나라도 true가있으면 true를 출력한다 + bool res = BT(board, idx + 1, row - 1, col) || + BT(board, idx + 1, row, col - 1) || + BT(board, idx + 1, row, col + 1) || + BT(board, idx + 1, row + 1, col); + + + //' '공백으로 바꾼 곳을 원상복구한다 => 그래야 여러 스타트 지점이 있어도 탐색을 이어나갈수있다 + board[row][col] = temp[idx]; + + return res; + + + + } + bool exist(vector>& board, string word) + { + temp = word; + + + for (int i = 0; i < board.size(); i++) + { + for (int j = 0; j < board[0].size(); j++) + { + //word[0]스타트 지점을 찾는다 그리고 BT가 T일때 찾는 문자가 있는것이다 + if (board[i][j]==word[0]&& BT(board, 0, i, j)) //스타트 지점에 4방향을 돌아봤을때 word가 있다는 의미이다 + { + + return true; + + } + } + + + + + } + + + return false; + } +}; +``` + + + +원상복구를 하는 부분 **board[row] [col] = temp[idx]** 이 부분이 있어야 스타트 지점이 여러개 있어도 탐색을 이어갈 수 있었습니다. + + + + +----------------------------------------------------------------------------------------------------------------------------------------------------- + + + + +### 7번 valid sudoku + +스도쿠가 유효한지 판단하는 문제입니다. 사실 이 문제는 Backtracking을 쓰지 않고도 풀수있는 문제입니다. 그래서 이번 문제는 backtracking을 사용하지않고 문제를 풀어 보겠습니다. + + + +**주의** +사실 이어지는 문제 8번을 풀다가 추천 문제에 있어서 풀어본 문제라서 backtracking관련 문제는 아니지만 이어지는 문제도 스도쿠 문제이기 때문에 이번 문제를 이해하시면 다음 문제를 이해하는데 도움이 될 것 같아서 이번 포스팅에 넣어 보았습니다. bactracking문제만 풀고 싶으시면 이 문제는 넘어가셔도 됩니다. + + + + +스도쿠의 규칙은 9 * 9 행렬에서 각 가로줄에는 중복되지 않는 숫자 1-9 ,각 세로줄 , 3 * 3 행렬도 마찬가지로 중복 되지 않는 숫자 1-9가 있어야 합니다 + +유효한지 판단하려면 위의 규칙을 지켰는지 확인을 하면 됩니다. + + + + +* c++ 코드 + +```c++ +class Solution { +public: + bool isValidSudoku(vector>& board) + { + //hash set 설정 + //각 row, col , 3*3행렬에 중복되지 않는 1-9를 넣는 해쉬이다 + unordered_set seen; + + //9*9 행렬 탐색 + for (int row = 0; row < 9; row++) + { + for (int col = 0; col < 9; col++) + { + //값이 있다면 + if (board[row][col] != '.') + { + + //각 row,col ,3*3행렬에 값이 있다는것을 표시 하기 위한 변수들 + string row_str = "R" + to_string(row) + board[row][col]; + string col_str = "C" + to_string(col) + board[row][col]; + string sub_arr = "B" + to_string(row / 3) + to_string(col / 3) + board[row][col]; //3*3 행렬 고려해야하기때문에 /3한다 + + //hash에 있다면 false + if (seen.find(row_str) != seen.end() || + seen.find(col_str) != seen.end() || + seen.find(sub_arr) != seen.end()) + { + return false; + } + //hash에 값 넣는다 (중복되지 않는 1-9) + seen.emplace(row_str); + seen.emplace(col_str); + seen.emplace(sub_arr); + + } + } + } + return true; + } +}; +``` + +해쉬 셋에 각 row, 각 col, 각 3*3행렬의 유효한 값을 저장합니다. 만약 해쉬셋에서 중복된 값을 발견하면 false를 반환하는 구조 입니다. + + +스도쿠가 유효한지 아닌지만 판단하면 되는 문제 였기 때문에 큰 어려움은 없었을 것 입니다. + + + +----------------------------------------------------------------------------------------------------------------------------------------------------- + + +### 8번 Sudoku Solver + + +앞에 7 번 문제를 보시고 왔다면 이 문제를 이해하는데 도움이 될것입니다. 앞의 문제와는 다르게 난이도가 있는 문제 입니다. +이번 문제는 스도쿠를 직접 푸는 문제입니다. + +빈 곳에 1-9중복되지 않는 숫자를 스도쿠 규칙에 맞추어서 풀어야합니다. + +그래서 입력으로 들어온 grid를 전체 탐색하면서 빈곳에 값을 넣어줄것인데 스도쿠 규칙에 따라 각 row,각 col , 각 3*3행렬에 넣으려는 값이 중복하는지 안하는지 확인을 하면서 값을 삽입해야합니다. + + + +* c++ 코드 + +```c++ +class Solution { +public: + pair isEmpty(vector>& board) + { + for (int i = 0; i < 9; i++) + { + for (int j = 0; j < 9; j++) + { + if (board[i][j] == '.') + { + return make_pair(i,j); + } + } + } + + return make_pair(10, 10); //9*9 matrix를 다 마친 경우 + + } + + bool check_row(int x, char val, vector>& board) + { + for (int i = 0; i < 9; i++) + { + if (board[x][i] == val) //각 row에 같은 val있으면 조건에 맞지않는다 + { + return false; + } + } + return true; + } + bool check_col(int y, char val, vector>& board) + { + for (int j = 0;j < 9; j++) + { + if (board[j][y] == val) + { + return false; + } + } + return true; + } + + bool check_box(int x, int y, char val, vector>& board) //sub box 모두를 탐색할것이다 + { + int X = x / 3; // 빈곳 row + int Y = y / 3; //빈곳 col 의 sub box위치 + + //각 sub box의 범위를 가리키기 위함이다 + X = 3 * X; + Y = 3 * Y; + + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + + //sub box에 같은 val 있으면 false + if (board[X + i][Y + j] == val) + { + return false; + } + } + } + return true; + + + } + bool solve(vector>& board) + { + pair empty_slot = isEmpty(board); + + if (empty_slot.first == 10 && empty_slot.second == 10) + { + //9*9 모든 경우를 다 탐색 한 경우이다 + + return true; //catch ans => 9*9 다 돈 경우 정답을 출력할수있다 + } + int row = empty_slot.first; + int col = empty_slot.second; + for (char val = '1'; val <= '9'; val++) + { + if (check_row(row, val, board) && check_col(col, val, board) && check_box(row, col, val, board)) + { + board[row][col] = val; //choose 조건에 맞는 다면 val로 교체한다 + + if (solve(board)) //Explore 계속 탐색해 나간다 + { + return true; + } + board[row][col] = '.'; //unChoose 조건에 맞지 않는 val이면 다시 원상 복구 한다 + } + } + return false; //Dead path 조건 맞지 않으면 backtracking 한다 + } + void solveSudoku(vector>& board) + { + + solve(board); + + } +}; + +``` + + +간단히 다시 리뷰를 해보면 빈곳에 1-9값을 넣어줄것인데 유효한지 각 row,col 3*3행렬 조건을 따져 줍니다. 계속 진행을 하다가 끝나는 조건 (9 * 9행렬 모두 돈 경우)일때 종료를 합니다. +그 전까지는 solve가 계속 탐색을 진행 합니다. 그래서 if문은 solve가 끝나는 조건을 만나야 비로소 연쇄적으로 true를 반환하면서 함수가 끝나게 됩니다. + +유효한 값의 가능성을 계속 탐색하면서 값이 유효하지 않다면 바로 이전으로 돌아가는 성질의 문제이므로 backtracking문제로 볼수있었습니다. +->스도쿠 조건에 따라 검사를 진행하는 과정은 7번 문제를 풀어보시면 더 쉽게 이해하실수있을 것 입니다. + + + + +----------------------------------------------------------------------------------------------------------------------------------------------------- + + +### 9번 N-Queen + + +마지막 문제는 N-Queen 문제입니다. 이 문제는 backtracking의 성질을 잘 보여주는 대표적인 backtracking 문제입니다. + + +n이 주어질때 n * n의 보드에서 n개의 퀸이 서로 공격하지 않도록 보드에 배치하는 문제입니다. 체스에서 퀸은 대각선 ,수직 , 수평으로 이동 할수있습니다. +서로 공격하지 않도록 배치해야 하므로 대각선 , 수평, 수직에 다른 퀸이 배치되어있으면 안됩니다. 이때 정답은 하나가 아닐수있습니다. + + + +8번 문제와 비슷한 유형의 문제라고 보시면 될것 같습니다. 스도쿠 문제를 풀때 또한 조건에 따라 처리를 해주었는데 이번에는 퀸의 성질에 따라 조건을 처리 하면 됩니다. + + + +우선 n = 4라고 가정하고 설명을 이어가겠습니다. 그러면 4 * 4 행렬에서 답을 찾아가야합니다. +0부터 16번째까지 모두 Q의 자리를 찾아서 탐색해 나간다면 O(4*16^(16+1))이 걸릴것입니다. 4는 왜 곱해지냐면 col과 대각선을 확인할때 걸리는 시간이 있기 때문입니다. + +저희는 이 Time을 더 최적화 해야합니다. 여기서 더 생각 해볼수있는것은 같은 row에 2개의 퀸이 올수없다는 것입니다. 그렇다면 각 row에 대해서 위치를 정해주고 이어 갈 수 있다면 + +TIME : O(4*4^(4+1))로 줄일수 있을 것입니다. (row을 따로 검사 하지 않아도 되는 이유 ) + + +아래 c++코드가 위와 같은 프로세스로 진행 되었습니다. + + + + + + +* c++ 코드 + + +```c++ +class Solution { +public: + + void set_board(vector& board, int& n) { // 보드 만들기 + + + board.reserve(n); //n개 만큼 크기 설정 + + //char vector로 입력이 오기 때문에 string으로 묶는 과정 + string s = ""; + for (int i = 0; i < n; i++) { + s = s + '.'; + } + + //묶은 string을 보드로 만드는 과정 + for (int i = 0; i < n; i++) { + board.emplace_back(s); + } + return; + } + + //퀸의 성질 확인하는 함수 + bool check_safe(vector& board, int& row, int& col, int& n) { + + + + for (int i = 0; i <= row; i++) { // col확인 같은 col에 Q있으면 false + if (board[i][col] == 'Q') { + return false; + } + } + + //대각선 확인할때 현재 row보다 아래의 대각선 위치는 고려 하지 않는다 + int i = row; + int j = col; + while (i >= 0 && j >= 0) { // 현재 row 기준 왼쪽 대각선 확인 + if (board[i][j] == 'Q') { + return false; + } + i--; + j--; + } + + i = row; + j = col; + while (i >= 0 && j < n) { //현재 row 기준 오른쪽 대각선 확인 + if (board[i][j] == 'Q') { + return false; + } + i--; + j++; + } + + + return true; + + } + + void nqueen(vector>& sol, vector& board, int row, int& n) { + + //n*n행렬이기 때문에 row가 n과 같아지면 모두 탐색 했다는 의미 이므로 종료 + //base case + if (row == n) { + + sol.emplace_back(board); + return; + } + + for (int i = 0; i < n; i++) { // n*n행렬이기 때문에 row, col모두 0~n-1까지의 범위 가진다 + + //각 row마다 퀸 자리 정해준다 + if (check_safe(board, row, i, n)) { + board[row][i] = 'Q'; + + //현재row에 Q넣고 다음 row부터 다시 탐색 한다 + nqueen(sol, board, row + 1, n); + board[row][i] = '.'; //중복하는 답을 찾기 위해 원상 복귀 한다 + } + } + + return; + } + vector> solveNQueens(int n) { + + vector> sol; //결과 배열 + + vectorboard; //n 배열 보드 + + set_board(board, n); //n 보드 만들기 + + nqueen(sol, board, 0, n); //탐색 시작 + + return sol; + } +}; + +``` + + +col과 대각선을 해결할때 O(N)의 time쓰는것 보다 O(1)으로 최적화 할수도 있는데 hashset을 이용해주면 가능합니다. + + +row에 Q정해졌다면 그때 col위치를 hash에 넣어줍니다 그리고 마지막에 row에서 Q넣을 때 기존 처럼 모두 검사하는것이 아니라 hashset에서 없는 위치를 찾아서 넣어주면 O(1)으로 해결 할 수있습니다. + + +대각선의 경우는 왼쪽 대각선과 오른쪽 대각선을 따로 생각을 해주어야 하는데 좌표를 (x,y)로 가정 해보겠습니다. + +왼쪽 대각선의 경우 x==y이기 때문에 x-y=0입니다. + +오른쪽 대각선의 경우 x+y의 값이 일정하다는 성질이 있습니다. 이 특성을 이용해서 hashset을 만들어준다면 대각선 처리 또한 O(1)으로 해결 가능합니다. + + +아래의 python 코드는 위의 프로세스의 결과 입니다. + + +* python 코드 + +```python +from typing import List + + +class NQueens: + def solve(self, n: int) -> List[List[str]]: + self.__results = [] + self.__col_set = set() #column duplicates + self.__diag_set1 = set() #row-col duplicates + self.__diag_set2 = set() #row+col duplicates + self.__n = n #length + + for x in range(n): + self.__bt(0,x,[]) + + return self.__results + + #python str is immutable 보드 만들기 + def __create_str_row(self, col:int) -> str: + str_list = ['.']* self.__n + str_list[col] = 'Q' + return ''.join(str_list) + + def __bt(self, row:int, col:int, board:List[str]): + #exit conditions base case + if row==self.__n or col==self.__n: + return + if col in self.__col_set: + return + + # 왼쪽 대각선 성질 + diag1_info = row-col + + #오른쪽 대각선 성질 + diag2_info = row+col + + # 설정한 hash에 값이 있다면 return + if diag1_info in self.__diag_set1: + return + if diag2_info in self.__diag_set2: + return + + #process + str_line = self.__create_str_row(col) + board.append(str_line) + + if len(board) == self.__n: + self.__results.append(board.copy()) + board.pop() + return + + #duplicates sets hash set 설정 + self.__col_set.add(col) + self.__diag_set1.add(diag1_info) + self.__diag_set2.add(diag2_info) + + #recursive calls + for x in range(self.__n): + self.__bt(row+1,x,board) + + #duplicates sets pop + self.__diag_set2.remove(diag2_info) + self.__diag_set1.remove(diag1_info) + self.__col_set.remove(col) + board.pop() + +nQsolver = NQueens() + +``` + + + + +----------------------------------------------------------------------------------------------------------------------------------------------------- + +### 마무리 + +이번 문제를 통해 bactracking에 대한 감을 잡는 시간이 되었으면 좋겠습니다. 특히 마지막 문제는 유명하고 대표적인 문제인 만큼 여러번 보고 이해하는 것을 추천 드립니다. + +긴 글 읽어 주셔서 감사합니다. + + + diff --git a/_posts/2022-03-05-DP.md b/_posts/2022-03-05-DP.md new file mode 100644 index 000000000000..343a2e0bbff2 --- /dev/null +++ b/_posts/2022-03-05-DP.md @@ -0,0 +1,205 @@ +--- +layout: single +title: "Dynamic Programming 개념 및 관련 문제" +categories: [Dynamic Programming,DP,Recursive,Climbing stairs,Min cost Climbing stairs,Minimum Path Sum,Conis Change,Decode way,Maximum Subarray,Maximum Product Subarray,Longest Palindromic Substring,Word Break ] +tag : [c++,python,Algorithm,C++,] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +### intro + +안녕하세요 이번 포스팅에서는 DP 즉 Dynamic Programming에 대해서 포스팅 할 것 입니다. + +DP는 큰 문제를 작은 문제로 나누어서 푸는 방식이라고 보면 될 것 같습니다. 그래서 problem과 sub problem을 정의하는게 매우 중요하다고 할수있습니다. + +분할 정복과 비슷해 보이지만 DP는 중복 될 수 있지만 분할 정복은 중복 될수 없다는 데에 차이가 있습니다. + +그래서 DP에서는 중복 하는 문제를 한번만 계산하기 위해 memoization을 사용하기도 합니다. + + +일반적으로 DP 문제는 특정 양을 최대화 또는 최소화하는 문제 , 특정 조건 또는 특정 확률 문제에서 배열을 계산해야하는 문제가 있습니다. + + + +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +### 피보 나치 문제 + +문제를 풀기 전에 피보나치 문제를 먼저 보면서 DP에 대한 감을 체험해 보도록 하겠습니다. + +피보 나치를 혹시 모르신다면 아래 링크에서 확인하세요 +[피보나치 위키백과](https://ko.wikipedia.org/wiki/%ED%94%BC%EB%B3%B4%EB%82%98%EC%B9%98_%EC%88%98) +{: .notice--danger} + + +DP문제에서 가장 힘든 부분은 개인적으로 상태들의 관계를 통한 점화식 부분일 것이라고 생각합니다. +그러나 피보나치 함수는 점화식을 알고 시작 하기 때문에 비교적 쉽습니다. + +일반적으로 처음 피보나치를 구현 하면 아래처럼 할 것 입니다. + + +* c++ 코드 + +```c++ + +int fibo_gener(int n) +{ + if (n == 0 ) + { + return 0; + } + if (n == 1) + { + return 1; + } + + return fibo_gener(n - 1) + fibo_gener(n - 2); +} + + +``` + +이런 식으로 구현 하게 되면 + +TIME :O(2^N)의 시간 복잡도를 가지게 됩니다. + + +지금까지의 단계는 DP문제로 치면 점화식을 그대로 코드로 구현한 것 입니다. +그러나 DP문제는 크게 Top down과 Bottom up으로 최적화를 할 수 있습니다. + + +일반적인 피보나치 함수를 Top Down방식과 Bottom up방식으로 최적화를 해보겠습니다. + +Top-down : 큰 문제를 작은 문제로 나누면서 푼다 -> 재귀로 구현 + +Bottom-up : 작은 문제 부터 차례대로 푼다 -> 반복문으로 구현 + + +첫 번째는 top down 방식인 memoization을 사용한 최적화 방법입니다. + +DP느 분할 정복과는 다르게 sub problem이 중복 될 수 있습니다. 그래서 중복되는 문제를 한번만 계산하기 위해서 memoization을 사용합니다. + +계산한 결과를 배열에 저장하면서 만약 memo배열에 값이 있다면 그 값을 그대로 사용하면서 중복 계산을 하지 않는 방식이다 + +* memoization c++ 코드 + + +```c++ + + +int memo[100]={0,}; +int fibonacci(int n) { + if (n <= 1) { + return n; + } else { + if (memo[n] > 0) { // memo의 값이 0이 아니면 + return memo[n]; // 그 값을 그대로 사용 + } + memo[n] = fibonacci(n-1) + fibonacci(n-2); + return memo[n]; + } +} +``` + + + +memoization을 이용해서 구현 하면 기존의 O(2^N)의 시간 복잡도가 O(N)으로 줄어들게 된다 + + + + +그러나 memoization은 오버플로우 문제가 있기 때문에 bottom up으로 문제를 풀면 좋다 + + +사실 피보나치 함수를 계산할때는 이전값과 이전이전값 즉 두개의 값만 있으면 계산할수있다. + + + + +* bottom up c++코드 + +재귀 사용하지 않고 반복문 사용한다. + + +```c++ + +long long int fibo_iter(int n) +{ + int prev = 0, cur = 1; + + if (n == 0) + { + return 0; + } + if (n == 1) + { + return 1; + } + + for (int i = 2; i <= n; i++) + { + int temp = cur; + cur += prev; + prev = temp; + + } + return cur; + + +} + + +``` + + + + + + + +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +### 문제 링크 + + + +[1번 Climbing stairs](https://leetcode.com/problems/min-cost-climbing-stairs/) +{: .notice--danger} + +[2번 Min cost Climbing stairs](https://leetcode.com/problems/min-cost-climbing-stairs/) +{: .notice--danger} + +[3번 Minimum sum Path ](https://leetcode.com/problems/minimum-path-sum/) +{: .notice--danger} + +[4번 Conis Change](https://leetcode.com/problems/coin-change/) +{: .notice--danger} + +[5번 Decode Way](https://leetcode.com/problems/decode-ways/) +{: .notice--danger} + +[6번 Maximum Subarray](https://leetcode.com/problems/maximum-subarray/) +{: .notice--danger} + +[7번 Maximum Product Subarray](https://leetcode.com/problems/maximum-product-subarray/) +{: .notice--danger} + +[8번 Longest Palindromic Substring](https://leetcode.com/problems/longest-palindromic-substring/) +{: .notice--danger} + +[9번 Word Break](https://leetcode.com/problems/word-break/) +{: .notice--danger} + +[10번 Partition Equal Subset Sum](https://leetcode.com/problems/partition-equal-subset-sum/) +{: .notice--danger} + + + +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + diff --git a/_posts/2022-04-17-compile_programming.md b/_posts/2022-04-17-compile_programming.md new file mode 100644 index 000000000000..9f7935df865d --- /dev/null +++ b/_posts/2022-04-17-compile_programming.md @@ -0,0 +1,34 @@ +--- +layout: single +title: "컴파일러 프로그램" +categories: [introduction,Compiler] +tag : [c,java,compiler] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +### 소개글 + +안녕하세요 **프로그램 언어와 컴파일러** 과목을 수강하면서 정리한 자료를 posting 할 생각 입니다. +평소에는 one note에 정리를 하는 편인데 github page와 연동 하는게 어렵 더라구요 그래서 pdf로 일단 내보내고 이 블로그에 게시 할 예정입니다. + +일단 chapter 별로 올릴 예정입니다. +저도 pdf로 다운 받고 추가로 공부를 진행하면서 메모가 추가 될 예정입니다. + +메모 되기 전에 pdf를 원하시는 분들을 위해 통합으로 만든 pdf 파일을 아래에 준비 해놓았습니다. + + + +중간고사이전내용pdf + + + +그리고 pdf안에 유용한 사이트의 링크도 포함 되어있으니 참고하실 분들은 참고하시면 될 것 같습니다. + + +마지막으로 내용중에 수정이 필요하거나 틀린 내용이 있다면 댓글로 반드시 알려 주세요! + +감사합니다. diff --git a/_posts/2022-04-20-AST.md b/_posts/2022-04-20-AST.md new file mode 100644 index 000000000000..c6082c84f09a --- /dev/null +++ b/_posts/2022-04-20-AST.md @@ -0,0 +1,79 @@ +--- +layout: single +title: "Abstract Syntax Tree" +categories: [Lexical Analysis,Scanner,Regular Expression,AST,JAVA,Compiler] +tag : [Lexical Analysis,lexer,lexical,scanner,parser,AST impliment of java,JAVA,java] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +### 시작 하며 + +Recursive Descent parsing algorithm을 이용해서 stream of tokens를 parse tree of AST로 변환하는 내용을 학습 할 수 있습니다. + + + + + +AST pdf + + + +### 과제 내용에 대한 본인의 답안 + + +저작권 문제가 있을 것 같아서 코드는 따로 공유 하지 않겠습니다. +필요하다면 그때는 댓글로 알려주세요!!! + + +### 내용 요약 및 정리 및 마무리 + +* Recursive Descent parsing algorithm을 이용해서 stream of tokens를 parse tree of AST로 변환하는 내용을 배운다 + + bnf는 left recursion 때문에 revursive descent parsing 사용 불가 -> EBNF로 바꾸어야 사용가능 EBNF는 recursive를 iterator로 바꾸었기 때문이다. + + + + * Token은 terminal이다 terminal이면 match() , Non terminal이면 함수를 call해야한다 + -> recursive + left recursive rule이 있다면 사용 불가능 (예 BNF) + + EBNF의 {} 같은 경우 0번이사반복을 인식하기 위해 Lookahead 방법을 사용한다. 즉 미리 보기이다. + +* AST 생성하기 + + 1. parsing 과정에서 AST생성 + 2. syntax tree를 AST로 변환 + + * parse tree에 internal non terminal 제거 + * separator와 puctuation(괄호) Terminal symbols 제거 + * 모든 사소한 non terminal 제거 + * 남아있는 non terminal을 leaf terminal로 대체 + + + + 흔히 방법 1로 AST를 생성한다 + + +* Abstract syntax of Clite를 통해 AST를 생성할 것 이다. + + EBNF of clite와 비교해서 어떠한 것이 바뀌었는지 보세요 + 일단 선언부와 실행부로 나눈것까지는 비슷하다 + int abc = 45; 이런식으로 구성이 안된다는 의미이다. int abc; abc = 45; 이런식으로 구성이 된다는 의미 입니다. + + + +**과제7보고서를 보면 lexer , AST, Parser 코드에 대해 이해 할수있을것으로 생각된다** + + + + + + + + + + diff --git a/_posts/2022-04-20-Clite.md b/_posts/2022-04-20-Clite.md new file mode 100644 index 000000000000..e4b466aaa9d3 --- /dev/null +++ b/_posts/2022-04-20-Clite.md @@ -0,0 +1,61 @@ +--- +layout: single +title: "Clite" +categories: [clite,Compiler] +tag : [compiler,clite,c,clite of c] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + + +### 시작 하며 + +Syntax part 2에서 살펴본 EBNF of Clite와 BNF of Clite의 연장선이라고 생각하면 될 것 같습니다. + + + + +Clite pdf + + + + +### 과제에 대한 본인의 답안 + + +Clite C 비교 과제 pdf + + +**cliet의 구문중에 c에 없거나 다른 요소** + +statement를 0번 이상 지원하는거 즉 null statement를 지원한다 + + +**clite program parse tree** + +pdf에 정의한 EBNF 가지고 parse tree 작성하면 되는데 내용이 길어서 생략된것이 있지만 흐름은 파악 할 수 있다. + + +**clite에 없는 C 특징** + + +주석,함수,다차원 배열, for 문 , case문, goto 문 , 초기화, 구조체,typedef, 삼항 연산자, pointer,++ -- 연산자, sizeof, shift operation + + + + + +### 내용 요약 및 정리 및 마무리 + + +* Clite 표현식을 통해 다시 한번 우선 순위가 낮은 방향에서 위의 방향으로가 먼저 표현되고 top down으로 진행 되는것을 확인 할수있습니다. + +|| -> && -> ==/!= -> <>,<=,>= -> +- -> */% -> unary -! 순으로 진행 합니다. 무슨말? + +Expression -> Conjunction - > Equop -> Relop -> Addition -> Term-> Factor-> primary 순으로 진행을 한다는 의미입니다. + + + diff --git a/_posts/2022-04-20-Lexer.md b/_posts/2022-04-20-Lexer.md new file mode 100644 index 000000000000..64dccfc5d4e1 --- /dev/null +++ b/_posts/2022-04-20-Lexer.md @@ -0,0 +1,557 @@ +--- +layout: single +title: "Lexical Analysis and Finite State Machine" +categories: [Lexical Analysis,Scanner,Regular Expression,Finite State Machine,Compiler] +tag : [Lexical Analysis,lexer,lexical,scanner,token,parser,Regular Expression,Regular Grammar,Finite automata,Finite State Machine,Lexer impliment of C,C,c] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + + +### 시작하며 + +Lexical Analysis(Scanner)에 대해 학습 할 수 있습니다. +그리고 Regular Expression과 Finite State Machine에 대해서도 학습 할 수 있습니다. + + +Lexer pdf + + + +### 과제에 대한 본인의 답안 + + + +토론 생각 답 + +1. pdf에 token의 RE표현식 있다 + +2. 여러 token 즉 keyword,id,type,operation등을 인식하는 sub automata를 통합하면 된다 + +3. source program에 오류가 있다는게 애매하긴했다. syntax문제가 있다면 parser의 synatax analyzer가 처리를 할 것이고 lexer에 정의 되지 않는 token이 있다면 lexer가 Error를 처리 할 것 이다. + +4. lexer level과 syntax level의 차이점 + + lexer는 token이 정의된 token인지 확인 밖에 못한다. 괄호가 matching이 되는지 적절한 변수명을 썻는지 등에 대한 문법 오류는 syntax level에서 처리 할 수 있다. + + +5. syntax error,sementic error,lexical error + +6. lexer가 token을 구분하면 parser에게 넘기고 parser가 만든 parse tree를 통해 syntax analayzer가 syntax level의 Error을 처리하면 될 것 같다 + + lexer는 문자가 잘못되었거나 이런건 detect할 수 있지만 문법적으로 틀린것인지는 탐지 할 수 없는데 syntax level의 오류를 어떻게 처리 하라고 한건지 잘 모르겠다 + +**Lexical Error는 character의 순서가 token pattern과 맞지 않을 때 발생한다.** + + +```c++ + +#define _CRT_SECURE_NO_WARNINGS +#define MAX_STR_SIZE 100 + +/* +* +* +* + 파일 읽고 파일 쓰기로 결과물 출력하는 프로그램 + + + +*/ + + + +#include +#include +#include +#include +#include +void get_token(char* str, FILE* ff); +int Check_keyword(char buffer[]); +int is_operator(char* str, int* idx); //operator확인하는 함수 +int is_parenthesis(char* str, int* idx); //괄호 확인하는 함수 + +//token 자료 구조 +char keywords[13][10] = { "int","float","double","char","if","else","while","for","break","continue","const","return"}; +char Operator[10] = {"><=+/*-%!"}; +char parenthesis[7] = {"[{()}]"}; +char OP[3] = {"=+-"}; + +int main() +{ + + FILE* fp,*fw; //파일 포인터 + char ch; + char str_buffer[MAX_STR_SIZE]; + char int_text[20]; //int count를 문자열로 변환할때 사용 + int count = 1; //line 행 count를 의미한다 + fp = fopen("input_lexical.txt","r"); //입력 파일 읽기 모드 + fw = fopen("output_lexical.txt", "w+"); + + //파일 읽기 이상 없는지 확인 + if (fp == NULL) + { + printf("Read the file ERORR\n"); + exit(1); + + } + + //파일 쓰기 이상 없는지 확인 + if (fw == NULL) + { + printf("Wirte the file ERORR\n"); + exit(1); + } + + //파일 읽기 + while (fgets(str_buffer,MAX_STR_SIZE,fp)) + { + //LINE n 처리한다 + //한줄씩 파일에서 가지고 온다 + fputs("\nLINE ", fw); + sprintf(int_text, "%d ", count++); + fputs(int_text, fw); + fputs(str_buffer, fw); + + // //주석 처리 + //주석이면 LINE만 출력하고 다음으로 넘어간다 + if (str_buffer[0] == '/' && str_buffer[1] == '/') + { + continue; + } + + + + get_token(str_buffer, fw); + + + + + } + + + fclose(fp); + fclose(fw); + + + return 0; +} + +//인자로는 파일에서 가지고온 한줄, 파일쓰기 포인터 +void get_token(char *str,FILE *ff) +{ + int left = 0; + int len = strlen(str); //str 길이 + + int operator_mode= 0; + int buffer_idx = 0; + char buffer[20]; + + char ch[4]; //operator 출력하기 위한 버퍼 + + int flag4 = 0; //goto ONE,TWO 처리 위한 flag + int flag5 = 0; ////goto THREE,FOUR 처리 위한 flag + + + + while (left token은 Regual Language인데 context free language보다 간단한 방법으로 정의 할 수 있다 + front-end 모듈화 분리하여 더 쉽게 구현 할 수 있다 + 이는 프로그램이 커지고 복잡해지는 것을 방지 할 수 있다. + + + +* Token is Terminal Symbols + + 문장에서 사용되는 최소 단위의 문법 요소 + + Regular Expression으로 표현한다 + (위에서 Token은 regual language라고 했는데 RL는 RE로 표현 할수있으므로 의미적으로 통한다) + +* Regular Language + + RG,RE,finite state machine으로 정의 가능 + + RG에서 집중해서 봐야할것은 정의한 구조이다 + 예제를 통해 RG가 정의한 구조를 적용해 보아라 (terminal Nonterminal or non terminal 구조이다) + + **a ㅌ Terminal** 인 a를 Regular Expression으로 표현 할 수 있다 + + + Finite automata + RE으로 이루어진 token을 판별할수있게 해주는 model + lexer가 token 구분할때 이 model을 사용해서 token을 구분 할 수 있다. + + + token구분 하기 위해 마지막에는 반드시 token에 포함되지 않는 symbol 읽어야한다 + + 마지막에 읽은건 pushback 처리를 해준다 + +* lexer 구현 + + token을 한번에 구분하지 않고 keyword,id,type등의 sub finite automata를 구성해서 통합하는 방식으로 구현 한다 + + 이때 마지막 글자는 push back과정을 하고 Start state로 돌아간다 + + push back은 마지막 요소를 읽었으면 읽지 않은 상태로 만들고 start로 돌아오는 역할을 한다 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_posts/2022-04-20-Syntax1.md b/_posts/2022-04-20-Syntax1.md new file mode 100644 index 000000000000..fe22cb324b13 --- /dev/null +++ b/_posts/2022-04-20-Syntax1.md @@ -0,0 +1,92 @@ +--- +layout: single +title: "Syntax part 1" +categories: [Syntax,Classifications of Languages,Compiler] +tag : [syntax part 1,compiler,grammar,Grammar,Derivation,BNF,Type of Grammar] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + + + +### 시작하며 + +Syntax에 관한 내용을 배울 것 입니다. 1과 2편을 나누었습니다. + + + + +Syntax1 내용정리 pdf + + + + +### 내용 요약 및 정리 및 마무리 + + +* Grammar는 {V_n,V_t,P,S} 총 4가지 요소로 이루어져 있다. + +* grammar와 language 사이의 관계 + +rule과 derivation rule의 차이를 Derivation을 통해 알수있었다 + +직접 유도를 한 것을 바탕으로 간접유도를 통해 특정 Grammar가 생성 할수 있는 요소인지를 판단 할 수 있다 + +leftmost는 왼쪽 부터 terminal을 확정 짓는 것이고 rightmost는 오른쪽 부터 terminal요소를 확정 짓는 것이라고 생각한다 + +Grammar Type에 따라 생성되는 Language가 다르다 + +동치는 생성결과가 같은것을 의미한다 + +예) +G1 = ({S}, {a}, P, S), where P: S -> a | aS +G2 = ({A}, {a}, P, A), where P: A -> a | aA + +G1 <=> G2 서로 동치 관계 이다. + +-> 모호한 표현식 공부 할 때 쓰일 예정 + + +* Write a grammar for each of the following languages. For Q5 ,use V_t = {(,)} + + 1. L1 = {a^n | n ≥ 0} + + a^0,a^1... + + 2. L2 = {a^nb^n | n ≥ 0} + + a^2,a^4b^3... + + 3. L3 = {ωω^R | ω ∈ V_T * }, where V_T = {a,b}. + + R은 reverse를 의미한다 + abba baab + + 4. L4 = {ω | ω = ω^R , ω ∈ V_T * }, where V_T = {a,b}. + + pailndrom 예시 + + 5. L5= {ω|ω:balanced parentheses}, where V_T = {(, )}. + + (),(()),()()(())...... + + + +simple matching lanuguage a^nb^n 은 CFL type2 + +double matching language a^n b^n c^n은 CSL type1 + +mirror image language L3같은 예제 CFL type2 + +palindrome language L4같은 예제 CFL type2 + +parenthesis language L5같은 예제 CFL type2 + + + + + + diff --git a/_posts/2022-04-20-Syntax2.md b/_posts/2022-04-20-Syntax2.md new file mode 100644 index 000000000000..f1919bc2ef7c --- /dev/null +++ b/_posts/2022-04-20-Syntax2.md @@ -0,0 +1,88 @@ +--- +layout: single +title: "Syntax part 2" +categories: [Syntax,Parse Tree,Compiler] +tag : [syntax part 2,compiler,EBNF,BNF of Clite,EBNF of Clite,Ambituity,parse tree,associativity and precedence] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + + +### 시작하며 + +Syntax part 2를 이어가 보도록 하겠습니다. + + +parse tree에 대한 개념을 배울수있고 결합성과 우선순위, 모호한 표현 처리 방법에 대해 학습 할수있습니다. + +그리고 Clite 문법에 관한 EBNF, BNF 표현식을 공부 할 수 있습니다. + + + + +Syntax2 내용정리 pdf + + + + + +### 과제 내용에 대한 본인의 답안 + + +사실 내가 작성한 보고서 내용과 비슷하긴 하지만 엄밀히 말하면 pdf안에 ebnf of clite 또는 bnf of clite의 내용이 정답에 가깝다 그걸 참고 하세요 + + +### 내용 요약 및 정리 및 마무리 + +* parse tree개념을 알 수 있었습니다. + + parse tree에서 우선순위 관계 start symbol,nonterminal symbol,terminal symbol과 대응 + 되는 node를 알 수 있었습니다. + + 아래쪽에 있을수록 우선순위가 높다 + +* 결합성과 우선순위에 관해 알 수 있었습니다. + + +,-,%,*는 좌결합 **은 우결합 관계 + +* 계산 식을 bnf든 ebnf로 나타낸때 고찰 + + 우선 순위에 따라 top down으로 표현하는 것 같습니다. + +/-에관한 Expr을 표현하고 */%를 표현할수있는 Term을 표현하고 + **을 표현할수있는 Factor 표현하고 + 마지막으로 숫자를 표현 할수있는 primary순으로 표현식을 이어가는 것 같습니다. + + **Expr은 Term을 포함하고 있고 Term은 Factor를 포함하고 있고 Factor는 primary를 포함하고 있습니다.** + + +* 모호한 표현 조건 + + 문장 A가 두개의 다른 parse tree를 가질때 + <=> 두개의 다른 leftmost derivations를 가질때 + <=> 두개의 다른 rightmost derivations를 가질때 + + **leftmost derivations와 rightmost derivations를 같이 쓰면 안된다** + + +* 모호성 해결 방법 + + 1. 문법을 처음 부터 모호성이 없도록 설계 + + 문법이 길고 복잡해진다 + + 2. Grammar이외의 추가 규칙 사용 + + +* EBNF + + {} iteration + () choice + [] option + + + + + diff --git a/_posts/2022-04-26-Operating_system_intro.md b/_posts/2022-04-26-Operating_system_intro.md new file mode 100644 index 000000000000..091b0e326448 --- /dev/null +++ b/_posts/2022-04-26-Operating_system_intro.md @@ -0,0 +1,30 @@ +--- +layout: single +title: "Operating system 학부 과정 정리 " +categories: [Operating System,OS,introduction] +tag : [Operating System,OS] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +### 시작하며 + +학부 과정에서 배우는 operating system 공부한 자료 정리하려고 합니다 + +제가 개인적으로 공부한 자료에 추가적인 필기와 추가적인 설명이 각 챕터마다 더해 질 것 입니다. + +제가 필기한 것을 원하지 않을 경우 아래의 pdf를 확인하시면 될것 같습니다.(단, 이후에 나오는 자료와 차이가 있을 수 있습니다.) + + +중간고사이전내용OSpdf + + + +compile_programming과 마찬가지로 진행 될 것 같습니다. 참고하시면 될 것 같아요 + +마지막으로 추가 사항이나 요구사항이 있다면 댓글로 남겨 주세요 + +감사합니다.! \ No newline at end of file diff --git a/_posts/2022-04-26-process_syncri.md b/_posts/2022-04-26-process_syncri.md new file mode 100644 index 000000000000..1abb7c30dd39 --- /dev/null +++ b/_posts/2022-04-26-process_syncri.md @@ -0,0 +1,276 @@ +--- +layout: single +title: "Process thread Synchronize" +categories: [Operating System,OS,process,synchronize,Mointor,semaphore,mutex,critical section,lock] +tag : [Operating System,OS,process,synchronize,Mutex,Critical Section,Critical Section Problem,Semaphore,monitor,lock] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + + + +### 시작하며 + +이번 포스팅에서는 semaphore,mutex,monitor에 대한 개념을 조금 더 추가 해서 정리 하겠습니다 + + +아래 pdf 링크에서 정리 했던 내용을 확인 할수있습니다. + +process_thread_syn_pdf + + + +--------- + + + + + +## critical section problem + + + +한 프로세스 내에서 타 프로세스들과 공유하는 영역에 접근하는 코드상의 영역이다. + +**다수의 프로세스들끼리 데이터가 공유되는 경우 프로세스들 간 critical section의 이용순서는 동기화가 되어야한다 동기화를 어떻게 할것인가에 대한 문제 이다** + + + +![ㅁㄴㅇㄹ없는 그림](C:\github_blog\meang123.github.io\images\2022-04-26-process_syncri\ㅁㄴㅇㄹ없는 그림.png) + + + + + + + +이를 해결하기 위한 기본 뼈대는 위의 그림과 같다 critical section들어가기 전에 즉 entry section에서 대기하는 영역 필요하고 critical section 이용 후에는 exit section에서 critical section 사용을 완료 했다고 알리는 영역이 필요하다 + + + +critical section problem이 해결 되기 위해서는 3가지가 충족 되어야 한다 + + + +1. **mutex Exculsion** + + - 하나의 프로세스가 임계구역 사용중이라면 다른 프로세스들은 임계구역 접근할수없다 즉 동시에 접근할수없다는 이야기이다 + + - 이 특성은 write only 파일 같은 경우 필요한 특성이다. + + - 하지만 Read only 처럼 수정이 불가능 하다면 상호 배타성 특성이 없어도 무방하다 + + + +2. **progress** + + - 임계구역에서 진행하는 프로세스가 없다면 대기중인 프로세스가 임계구역을 사용해야함을 의미한다 즉 진행성이라고 보면 된다 + + + +3. **Bounded wating** + + - 모든 프로세스들은 임계구역에 한정된 시간 내에 진입 해야한다 즉 하나의 프로세스가 무한한 시간동안 임걔구역을 사용할수없다 + - 기근 현상을 방지 하기 위함이다 + + + + + +이 문제를 해결 하기 위해 + +HW solution -> mutex + +SW solution -> semaphore + + + + + +---------------- + + + +## Mutex (HW solution) + + + +가상의 잠금 장치인 lock을 이용해서 임계구역 동기화하는 방식이다. + +lock은 sharable resource이다. + + + +lock을 획득하는 함수는 atomic operation이다 + + + +atomic operation + +* 실행중에 간섭받거나 중단되지 않는다 +* 같은 메모리 영역에 대해 동시에 실행되지 않는다 +* context switching 일어나지 않음이 보장 된다 + + + + + +![화면 캡처 2022-07-19 162618](C:\github_blog\meang123.github.io\images\2022-04-26-process_syncri\화면 캡처 2022-07-19 162618.png) + + + +> lock이 없어야 while문에서 빠져 나오고 cs 영역으로 들어갈수있다 +> +> cs영역 사용 이후 lock을 해제 해준다 +> +> 위의 구조는 mutex exculsion 만족하지만 bounded waiting은 만족하지 못한다 + + + +**pros** + +멀티 코어 환경이고 cs이용 시간이 평균적으로 적다면 오히려 lock획득 위해 계속 while문 돌게 하는게 전체적인 overhead줄이는 방법일수있다 + + + +**cons** + +* busy waiting : lock 얻기 위해 기다리는 프로세스/스레드들은 cs 진입위해 대기중인 상태임에도 계속 실행 상태인 현상 + +​ 즉 대기하는데 실제로는 while문 때문에 cpu 사용하고 있다 -> cpu 사이클 낭비 + +* 우선순위 역전 현상 발생할수도 있다. 실제로 우선순위가 높은 프로세스가 lock을 취할수없어서 대기 해야하는 상황 + + + + + +-------------------------------- + + + +## Semaphore (SW solution) + + + +하나이상의 프로세스 / 스레드가 critical section에 접근 가능하도록 하는 것이다. + +뮤텍스에는 임계영역에 접근 가능한 쓰레드 개수를 조절하는 기능이 없다. 그러나 세마포어는 가지고 있다. + + + +* binary semaphore +* counting semaphore + + + +wait,signal로 조작 된다 wait 하거나 signal중에는 semaphore 변수를 변경하는 과정이기 때문에 독립적으로 발생해야한다 + + + +**busy waitng**을 해결하기 위해 아래와 같이 작동 시킨다 -> block 시켜서 block list로 넣는것으로 해결 + + + + + +![제목 dfdfd그림](C:\github_blog\meang123.github.io\images\2022-04-26-process_syncri\제목 dfdfd그림.png) + + + + + +counting semaphore에서 세마포어 값 S는 공유되는 자원들의 개수(협동 프로세스/스레드 간 접근 하도록 허용된)로 초기화 된다 + + + +예를 들어 최초에 프로세스 1, 프로세스 2, 프로세스 3이 협동 관계에 있고 공유되는 메모리 영역에 총 10개의 변수가 있다 가정하면 초기 계수 세마포어는 10의 값을 가진다 + + + +그러나 세마포어는 deadlock, starving의 문제가 남아있다. 또한 wait, signal의 순서가 변경 되거나 하면 오류가 발생하는 구조이다 + + + +---------- + + + +## Monitor + + + +lock에 wating queue 뿐 아니라 conditional variable에도 wating queue가 있는 모델이다 + +즉 각 conditional variable마다 하나의 대기 큐를 가지고 있는것이다. + + + +* 한번에 하나의 스레드만 실행돼어야 할때 +* 여러 스레드와 협업이 필요할때 + + + +wait 상태 들어가기 전에 lock을 release하고 들어간다 + + + +**condition variable** + +조건을 기다리는 스레드들이 대기하는 공간인 waiting queue를 가집니다. condition variable은 세 가지 동작이 있는데 아래와 같습니다. + + + +**- wait()** : 한 스레드가 condition variable에 데고 이 동작을 수행하면, 자기 자신을 이 condition variable의 waiting queue에 넣고 대기 상태로 전환하게 됩니다. + + + +**- signal()** : 한 스레드가 condition variable에 데고 이 동작을 수행하면, condition variable의 waiting queue에서 대기 중인 스레드 중 하나를 깨우게 됩니다. + + + +**- broadcast()** : 한 스레드가 condition variable에 데고 이 동작을 수행하면, condition variable의 waiting queue에서 대기 중인 스레드 전부를 깨우게 됩니다. + +**[출처]** [동기화에서 모니터는 이렇게 사용됩니다](https://blog.naver.com/myca11/222650655740)|**작성자** + + + + + +### product/consumer problem with monitor + + + +![화면 캡처 2022-07-21 155003](C:\github_blog\meang123.github.io\images\2022-04-26-process_syncri\화면 캡처 2022-07-21 155003.png) + +​ **[출처]** [쉬운코드 유튜브 모니터 영상](https://www.youtube.com/watch?v=Dms1oBmRAlo)|**작성자** + + + + + + + +product /consumer 문제에서 공유되는 자원은 buffer q이다. fullCV,emptyCV는 conditional variable이다. 각 wating queue를 가지고 있다. + +producer는 buffer q가 가득 찼을때 fullCV wating queue에 삽입 한다 consumer는 buffer q가 비어 있으면 emptyCV wating queue에 삽입 하는 구조이다. + + + +멀티 스레드 환경이므로 여러가지 상황이 발생할수있다. 그리고 wating queue에서 스케쥴링 방식은 구현에 따라 다를수있다. + +또한 운영체계 시스템 설계가 또 다를수 있기 때문에 wait는 반드시 while문 안에서 작동해야한다 + + + +signal은 하나만 깨우는것이고 broadcast는 모두를 깨우는 작업이다. signal에서는 대기 큐에 있는 task를 ready Queue로 보내서 실행 시킬수있게 한다. + + + + + + + diff --git a/_posts/2022-06-04-TypeChecker.md b/_posts/2022-06-04-TypeChecker.md new file mode 100644 index 000000000000..077a21b0bced --- /dev/null +++ b/_posts/2022-06-04-TypeChecker.md @@ -0,0 +1,56 @@ +--- +layout: single +title: "Static Type checker " +categories: [CliteP,JAVA,TypeChecker,Compiler] +tag : [clite,cliteP,TypeChecker,Static type checker,JAVA,java,Function,Call Statement, Call Expression] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +### 시작 하며 + +AST에서 나온 결과로 type checking을 하게 됩니다. + +<변수,타입>의 typeMap 자료구조를 만들어서 타입을 확인합니다. + +clite에서 type check를 어떻게 진행하는지는 정리한 pdf에서 알 수 있습니다. + + +원노트에서 정리한 기반의 내용의 pdf는 아래 링크에서 확인 할 수 있습니다. + + +Static Type Checker one note pdf + + + + + +### 내용 요약 및 정리 및 마무리 + +함수의 내용도 타입 체크 어떻게 하는지 포함하겠다 + +Type checker는 변수와 타입을 typeMap 자료구조에 삽입한다. + +V()함수로 타입 체크를 하는데 인자에 무엇이 오는가에 따라 역할이 다르다 + +1. Declarations 같은 경우는 중복 선언이 되었는지 확인 + +2. Declarations, Functions를 인자로 받는 경우 선언부의 이름과 함수의 이름이 같지 않는지 확인을 한다. + +3. Functions을 인자로 받는 경우 Declarations와 마찬가지로 함수끼리 이름이 중복되지 않는지 확인을 한다. + +4. Functions, TypeMap이 인자로 온 경우 함수의 parameter, locals의 내용을 선언부 배열에 넣어서 V(Declarations)를 통해 유효한 parameter, locals인지 확인을 한다. + +함수가 main이 아니라면 void형이 아닌데 return이 없는 경우에 대한 유효 체크를 한다. 또 Void형인데 return 형이 있는 경우에 대한 처리도 하고 main 함수에 return이 있는지에 대한 유효 확인도 한다. + +5. Statement, Typemap을 인자로 받은 경우 Assignment의 경우는 Variable은 typeMap에 정의 되어있는지 확인하고 Expression은 유효한지 확인을 한다. 또 변수와 할당하는 값의 타입이 같은지 확인을 해야한다. 만약 같지 않다면 float일때는 int형 , int 형일때는 char형까지만 지원하고 나머지는 지원하지 않느다. +Loop, condional의 경우 조건식이 bool 타입인지 확인을 해야한다. 그리고 conditional은 than statement, else statement가 유효한지 판단하기 위해 V()를 재 호출한다. LOOP의 경우도 body가 유효한지 확인을 하기 위해 재 호출을 한다. + +callStatement는 함수에 대한 타입 유효 검사를 진행하는데 전달하는 인자와 parameter개수가 같은지 부터 힘수에 대한 유효 검사를 진행한다. 또 함수의 이름을 선언 하지 않는경우데 대한 에러도 같이 확인한다 + +6. Expression, typemap이 인자로 오는 경우 변수가 선언이 되었는지 아닌지 유효검사를 하고 Binary같은 경우에는 계산하는 변수끼리 타입이 같은 지 확인을 한다. Unaray에서는 !가 오면 bool type으로 유효 검사를 하여야하고 NegateOp가 오면 int형으로 처리를 해야한다. unary에서는 타입 변환도 같이 처리를 한다. Callexpression의 경우는 함수이름이 없는 경우, 함수가 void인데 1+f(1)하면 안되는 경우, 전달하는 인자 개수와 받는 parameter 개수가 일치하는지에 대한 유효검사를 진행한다. callsatement에서 처리하는 유효검사와 비슷하다. + + diff --git a/_posts/2022-06-05-Deadlock.md b/_posts/2022-06-05-Deadlock.md new file mode 100644 index 000000000000..bbe462024f16 --- /dev/null +++ b/_posts/2022-06-05-Deadlock.md @@ -0,0 +1,11 @@ +--- +layout: single +title: "Deadlock " +categories: [Operating System,OS,Deadlock,] +tag : [Operating System,OS,Deadlock] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- \ No newline at end of file diff --git a/_posts/2022-06-05-Functions.md b/_posts/2022-06-05-Functions.md new file mode 100644 index 000000000000..1c3ba010ba66 --- /dev/null +++ b/_posts/2022-06-05-Functions.md @@ -0,0 +1,68 @@ +--- +layout: single +title: "Functions Semantics " +categories: [CliteP,JAVA,Functions,Function,Activation Record,Compiler] +tag : [CliteP,JAVA,Functions,Function,Activation Record,static link, dynamic link,NonLocal,chain offset, local offset,Runtime stack, heap] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + + +### 시작하며 + +메모리 구조와 함수의 구조를 알수 있습니다. + +중첩 함수가 지원 되는 pascal에서 함수는 어떻게 call이 되는지 전반적인 느낌을 알수있습니다. +Activation Record의 개념과 static link, dynamic link의 구조에 대해서 알수있습니다. + + +Function_Semantic one note pdf + + + +### 내용 정리 및 마무리 + +* pass by value,reference,value result + +* Argument와 Parameter 차이 + +* stack(static,runtime) <-> Heap의 메모리 구조 + +* Runtime memory organization + +* Activation Record + - local + - parameter + - return address + - result + - static link , dynamic link + +* chain offset, local offset + +* Example 문제 분석 + +**static link, dynamic link 개념 안다는 가정** + +컴파일 시간에 (chain offset, local offset) binding 하면 +런 타임 시간에 binding한 정보 가지고 변수를 찾는다 + + +마지막 그림에서 봐야할 것은 sub 1의 static link가 어디로 연결 되어있는지(BigSUB) + +sub2의 static link는 어디로 연결 되어있는지 (BIGsub) + +그러면 sub1에서 B는 어디서 찾고 있나 BIGSub에서 찾고 있다 +sub3의 A는 어디서 찾고 있나? statick link 2번 타고 들어가서 BIGSUB에서 찾고 있다 + + +실제 프로그램 구현 할때는 변수가 해당 stackFrame에 없으면 연결된 static link를 타고 찾을수있도록 구현 했다 + +그리고 실제 구현한 프로그램에서는 중첩 함수 지원 하지 않는다 + +global stackFrame 안에 main 등 여러 함수가 들어가는 방식 +이렇게 되면 global stackFrame안에 있는 함수들의 static link는 모두 global stackFrame을 가리키게 되고 dynamic link 구조만 다르게 진행 될 것이다. + + diff --git a/_posts/2022-06-05-Semaintics.md b/_posts/2022-06-05-Semaintics.md new file mode 100644 index 000000000000..f4bb6af08a70 --- /dev/null +++ b/_posts/2022-06-05-Semaintics.md @@ -0,0 +1,67 @@ +--- +layout: single +title: "Semantics " +categories: [CliteP,JAVA,Functions,Function,Semantics,Compiler,Meaning Function] +tag : [CliteP,JAVA,Functions,Function,Semantics,Compiler,Meaning Function,State,Stack,Stack Frame,Frame Stack] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +### 시작 하며 + +간단한 내용 정리와 구현 한 프로그램에서 semantics를 어떻게 구현 하였는지 배운점을 기록 하겠습니다. + +아래에 원노트로 정리한 파일이 있습니다. + + +Semantic one note pdf + + + +### 내용 정리 및 마무리 + +* short circuit으로 프로그램을 더 간결하게 만들 수 있다 + +* 수학과 컴퓨터 계산은 같지 않음 예) overflow + +* 선언만 되어있으면 undef로 되어있고 할당되거나 값이 바뀌면 다시 설정 된다 + +* copy, reference 차이 + * copy한 값은 서로 영향을 주지 않음 -- have the same value + * reference는 서로 영향을 받는다 -- point the same object + + +* Environment , State + + - Environment + * name-> memory location 대응 시킴 "" + + - State + * memory location -> value 대응 시킴 "<154,5>" + * identifier, value 집합으로 정의 + + +* **실제 프로그램 구현 내용** + +Semantic를 구현하기 위해 3가지 class를 추가로 구현했다. FramState, StackFrame, State이다. + +FrameState는 interpreter 실행중에 발생하는 변수와 대응하는 값에 집합이다. HashMap 자료구조를 통해 key에 변수, value에 변수값을 넣는다. onion 함수는 변수와 값을 주면 변수의 값을 파라메터로 넘긴 값으로 바꿔주는 함수이다. + +StateFrame은 Activation Record에 해당하는 클라스이다. StateFrame은 함수이름, static link, dynamic link, FrameState로 구성되어있다. 마지막으로 State에서는 만든 stackFrame을 stack에 push, pop 하는 역할을 하고 stack에 있는 stackFrame을 처리하는 클라스이다. static link , dynamic link 설정이 이루어지고 variable, value를 재설정 하는 역할도 이 클래스가 한다. +Semantics 클래스를 보면서 각 클래스에 세부적인 내용을 살펴보겠다. + +**1 semantics** +**1.1 M(program p )정의** +**1.2 initialState(program p) 정의** + +meaning Function에 program이 인자로 들어가면 initial()함수가 실행이 되는데 전역변수에 대한 stackFrame을 만든다. 그리고 전역변수 globals에 대한 stackFrame과 program안에 정의된 함수들을 state 생성자에서 초기화 한다. 이때 stack이 생성이 되고 현재 함수를 나타내는 current_func이 초기화 된다. +다시 M(Program p)로 돌아오면 main함수에 대한 StackFrame을 생성한다. 이때 static link, dynamic link와 새로운 FrameState가 생성이 되고 state stack에 push된다. M(program p)은 현재 함수의 M(current_func.body,stat)를 반환하면서 계속 실행이 이어진다. + +**1.3 M(Statement)** +Statement의 종류에 따라 meaning function을 처리하였다. Assignment는 <변수, 변수 값>을 state stack top의 StackFrame에 값을 대응시킨다. 만약에 assignment의 <변수, 변수 값>이 Stack top StatckFrame에 없다면 static link를 타고 들어가서 값을 찾아서 대응시킨다. Block의 경우는 모든 statement에 대해 meaning function을 호출한다. Block에서 return이 있다면 즉시 반환을 한다. Loop의 경우도 조건식을 확인해주고 Loop안에 return 문 있는 경우 바로 반환을 해주어야 하기 때문에 확인하는 작업을 해준다. CallStatement의 경우는 함수가 넘겨주는 인자를 배열에 저장을 하고 state stack에 push를 해주면서 current_func을 push하는 StackFrame으로 바꿔준다. 그리고 byValue() 함수를 통해 저장한 argument를 함수의 parameter에 넘기는 작업을 한다. Parameter로 argument를 넘기면 그때 정의한 함수의 실행부가 실행이 된다. 실행이 완료되면 stack에서 pop을 한다. 이때 dynamic link가 가리키는 StackFrame이 current_func이 되고 pop을 한다. Return meaning function을 보면 <$ret, 반환 값>을 Stack에 top에 있는 StackFrame에서 찾아서 업데이트 한다. 만약 없다면 static link를 타고 들어가서 대응하는 변수 찾아서 수정한다. M(Statement)의 반환 타입은 State이기 때문에 state stack에 저장 되어있는 StatackFrame의 내용이 수정이 되거나 Stack이 조정이 되거나 등의 작업을 한다. + +**1.4 M(Expression)** +Expression도 종류에 따라 meaning function을 처리한다. M(Expression)은 Value를 반환 타입으로 가지기 때문에 Value를 반환한다. 일반 변수 값 Value라면 Value를 그대로 반환한다. VariableRef라면 변수에 대응하는 값을 stack에 top에서 value를 찾는다. 만약 없다면 static link를 타고 찾아서 반환한다. Binary인 경우 meaning function을 통해 expression term1, term2를 value로 바꿔주고 operation 정보와 함께 applyBinary 함수를 실행한다. 이 함수는 Binary에서 실행하는 연산 결과에 대한 Value를 반환해주는 함수이다. Unary인 경우도 Binary와 마찬가지로 meaning function을 통해 expression term1을 value로 전환하고 applyUnary 함수를 적용해서 Unary대한 결과를 Value로 반환한다. 마지막으로 CallExpression을 살펴보면 callstatement와 비슷한 구조로 진행이 된다. parameter로 넘기는 인자를 args 배열에 저장을 하고 state stack에 새로운 함수에 대한 StackFrame을 push한다. current_funct을 현재 statckFrame으로 바꿔준다. 함수의 실행부를 실행하고 stack에서 pop하는 구조까지 비슷하게 흘러가지만 다른 점은 return에 대한 처리이다. Return meaning function은 state에 variable과 expression처리한 value를 새로 업데이트 해주고 saw_ret 불리언 변수를 true로 바꿔준다. CallExpression에서는 return 문에 함수가 올 수도 있기 때문에 이에 대한 처리가 추가적으로 이루어진다. block안에서 return f()함수가 있었다고 하면 return이 있기 때문에 saw_ret = true가 된다. 그리고 f()에 대한 처리가 callExpression에서 이루어질 때 temp_saw_ret = saw_ret로 상태 값을 임시 저장한다. 이렇게 하는 이유는 f()함수의 실행부가 처리될때 saw_ret가 true로 되어있으면 제대로 작동을 하지 않기 때문에 일단 saw_ret = false로 처리하고 body를 정상적으로 처리하기 위함이다. F()의 처리가 끝나면 그때 temp_saw_ret에 저장 되어있던 상태 값을 saw_ret에 저장시키고 return처리를 한다. diff --git a/_posts/2022-06-07-Bottom_up_parser.md b/_posts/2022-06-07-Bottom_up_parser.md new file mode 100644 index 000000000000..18878c422103 --- /dev/null +++ b/_posts/2022-06-07-Bottom_up_parser.md @@ -0,0 +1,66 @@ +--- +layout: single +title: "Bottom up Parser " +categories: [CliteP,JAVA,Bottom up Parser, LR parser,Compiler] +tag : [CliteP,JAVA,Top down parser, LR parser,Conflict,Parsing Table, rightmost derivation] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +### 시작하며 + +Bottom up parser에 대한 내용과 LR Parser에 대한 내용 및 parser table 적용하는 내용은 아래 PDF 링크에 내용이 있습니다. + + +Bottom up Parser One Note PDF + + + +### 내용 정리 및 마무리 + + +* **LR parser** + + - bottom up parser + - rightmost derivation in reverse + - LL parser보다 poweful -> 대부분의 programming language에서 구문 분석 가능하다 + + +* **용어 정리** + + - **reduce** + + - A-> B인 경우 B => A로 역변환하는것을 의미한다 + - pdf 예를 들어보면 A->b 의 production이 있을때 abbcde에서 aAbcde로 바뀐것을 예로 들 수 있다. [b가 A로 reduce 되었다] + + - **handle** + + - abbcde => aAbcde 역변환 할때 handle b를 non terminal A로 치환함 + + - **handle pruning** + - handle b를 A로 reduce하는 것을 handle prunning이라고 한다 + + + +* LR parsing은 handle pruning을 반복하여 start symbol로 reduce한다 + +* pdf에 parser example있는데 step을 따라가면서 직접 해보기를 바란다 + +* LALR은 모호한 문법 파싱 못한다 + + - conflict 발생하기 때문이다 + +* conflict 발생해도 precdence와 associativity를 이용해서 해결 가능 + +* Conflict 종류 + + - **Shift reduce conflict** + + ○ Shift와 reduce 모두 가능한 상황 + + - **Reduce- reduce conflict** + ○ 두가지 이상의 rule로 reduce 가능 + diff --git a/_posts/2022-06-07-Top_Down_parsing.md b/_posts/2022-06-07-Top_Down_parsing.md new file mode 100644 index 000000000000..343ae84ea7ad --- /dev/null +++ b/_posts/2022-06-07-Top_Down_parsing.md @@ -0,0 +1,90 @@ +--- +layout: single +title: "Top Down Parser " +categories: [CliteP,JAVA,Top down parser, LL parser,Predictive parser,Compiler] +tag : [CliteP,JAVA,Top down parser, LL parser,First,Follow,Parsing Table, leftmost derivation,Predictive parser] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +### 시작하며 + +Top down parser중에 Backtraking이 없는 predictive parser에 대해서 정리한 pdf가 아래에 링크에 있습니다. + +Top Down Parser One Note PDF + + + + + +### 정리 및 마무리 + + +* **LL parsing** : 왼쪽에서 스캔해서 파싱하는 방식 , left most dervation + +* **Nondeterministic top down parsing** : 규칙 선택이 잘못 된 경우 backtracking 필요하다 + +* **Deterministic top down parsing** -> predictive parser : Backtracking을 배제 한 것이다. + + - lookahead 방식 : LL(1) -> one toekn lokkahead top down predictive parser + + - lookahead는 frist , follow함수를 이용해서 구현한다. 이러한 정보 가지고 parsing table을 만든다 + + - parsing table에 action이 2개 이상이 오면 모호해진다 -> 충돌 발생 + + +* **left recursion은 infinite loop 발생한다** + + +* parsing table이 주어졌을때 스택에 거꾸로 push한다는것만 인지하고 진행 하면 문법 G에서 해당 input string이 유효한지 아닌지 확인 할 수 있다. + + - LL(1) parsing은 잘못 되면 backtracking 하는데 backtracking 하지 않으려면 LL condition을 만족해야한다 -> Frist, Follow 함수 알고리즘 필요 + + - 모든 문법이 LL parsing 할 수 없다 + + +* **First (X) 함수** + + - X가 생성하는 스트링의 시작 위치에 올 수 있는 terminal의 집합 x=>* 입실론 이면 입실론도 포함이다. + + - pdf에 나와 있는 예제로 설명을 보충하자면 E <- T <- F 관계가 되잖아 이런 이유는 Frist E에서 F 까지 non terminal로 계속 들어가서이다 F에서 결과물이 나오니까 관계 가지는 함수들도 같은 결과를 가지게 된다 + + +* **Follow (X) 함수** + + - X라는 symbol뒤에 나올수 있는 symbol들을 포함한다 + + - 입실론은 없어야한다 + + - 즉 유도 과정에서 x뒤에 올수 있는 terminal의 집합이다 + + - x가 start symbol이면 $가 온다 + + - 보충 설명 + + - Follow(X) : + + - a. Y -> αXβ => First (β) - ε + - b. Y -> αXβ && ε ㅌ First(β) [First β가 ε 생성한다면 ] => Follow(Y)이다 + - Y-> αX => Follow(Y) + + + - pdf에 내용을 보충해보면 Follow E' = Follow E' U Follow E인데 Follow E'이 recursive이니까 결국에 Follow E와 동일한 결과를 얻게 된다 + + +* **LL condition** + + - A-> a | b + + 1. First (a) n First(b) = 공집합 + + 2. if a가 입실론을 생성한다면 Follow(A) n Follow(b) = 공집합 + + + 2 가지 조건을 만족해야 ll codition 만족 하는 것이다. + + + diff --git a/_posts/2022-06-07-lex_yacc.md b/_posts/2022-06-07-lex_yacc.md new file mode 100644 index 000000000000..ac3a3c5d318a --- /dev/null +++ b/_posts/2022-06-07-lex_yacc.md @@ -0,0 +1,26 @@ +--- +layout: single +title: "Lex and Yacc " +categories: [Lex,Yacc, Compiler] +tag : [lex,Yacc,compiler] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +### 시작 하며 + +lex 와 yacc에 대해 간단하게만 언급 되어있습니다. 자세한건 온라인에서 메뉴얼을 찾아 봐야합니다. + + + +lex yacc One Note PDF + + + + +### 정리 및 마무리 + + diff --git a/_posts/2022-07-09-Basic_of_OS.md b/_posts/2022-07-09-Basic_of_OS.md new file mode 100644 index 000000000000..d49c4b5305b6 --- /dev/null +++ b/_posts/2022-07-09-Basic_of_OS.md @@ -0,0 +1,135 @@ +--- +layout: single +title: "Basic of OS " +categories: [Operating System,OS,Basic] +tag : [Operating System,OS] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +### 시작하며 + +os 내용 들어가기 전에 전체적인 그림을 보는 파트 입니다. +전체적으로 컴퓨터 구조가 어떻게 이루어져 있는지 알 수 있습니다. + +Basic_of_OSpdf + + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + + +### 내용 정리 + +컴퓨터는 여러개의 cpu와 장치들로 이루어져 있다. 실행시키려면 메모리에 load되어있어야 한다 + + + +![화면 캡처 2022-07-09 194116](C:\github_blog\meang123.github.io\images\2022-07-09-Basic_of_OS\화면 캡처 2022-07-09 194116.png) + + + +> **OS 정의와 관련 키워드** +>* 하드웨어와 유저 사이의 interface이다 +>* Bootstrap program + - 컴퓨터가 시작할때 작동하는 프로그램 + - ROM에 저장되어있다 + - OS 를 load할 위치와 system을 실행할 방법을 알고 있다 + - OS kernel에서 위치되어있고 load된다 + + + + +* **interrupt** + - event에 의해 발생한다 + + +* **system call** + +* OS는 각 device controller에 대해 device driver를 가지고 있다 + + + +> **IO Operation** +> +> * **device controller** +> +> * Hardware program +> +> * 장치와 OS에서 명령을 수신하는데 사용할수있는 모든 종류의 소프트웨어간의 고속도로 역할을 한다 +> +> * local buffer를 가지고 있다 +> +> * 1. Input register, output register(Data register) - for data +> +> 2. Control register - CPU가 보낸 I/O 명령을 임시저장하는 역할 +> +> 3. Status register - Data Register의 상태를 표시 받을 준비가 됐는지 안됐는지 +> +> +> +> * **device driver** +> * device와 OS 사이의 interface 역할을 한다 +> * 다른 종류의 OS의 상호작용을 위한 software program +> * + +![blog_2](C:\github_blog\meang123.github.io\images\2022-07-09-Basic_of_OS\blog_2.png) + + + + + +> device driver는 device controller안에 레지스터에 적절한 레지스터 내용을 load한다 (예 키보드에서 친 글자에 대한 레지스터 내용 cpu는 받은 내용이 키보드에 어떤 글자인지 모르니까 키보드 device controller에게 관련 레지스터 내용을 넘긴다 ) +> +> dvice controller안에 loca buffer가 가득 차면 device controller는 device driver에 interrupt 걸어서 알린다 + + + +> 많은 디바이스가 있으면 interrupt 많이 걸리는 구조(per bit 마다 interrupt를 걸기 때문이다) 이기 때문에 DMA라는 구조를 사용해서 해결한다 원래는 cpu를 통해서만 메모리 접근이 가능한 구조인데 DMA controller도 메모리에 접근할수있게 함으로써 많은 IO deviec 처리 성능을 올린다 +> +> local buffer 작업이 다 끝나면 메모리로 복사하는 작업을 해주고 interrupt를 발생시켜서 한번에 처리하는 방식이다 +> +> -> per block 마다 interrupt한다 + + + +---------------------- + + + +## *프로세스 A가 디스크로부터 파일을 읽어오는 명령을 실행한다고 했을 때 내부적으로 일어나는 과정은 다음과 같다.* + + + +1. 프로세스 A가 시스템 콜을 요청하면서 CPU 내에 인터럽트 라인을 세팅한다. + +2. CPU는 실행 중이던 명령어를 마치고 인터럽트 라인을 통해 인터럽트가 걸렸음을 인지한다. + +3. mode bit를 0으로 바꾸고 OS에게 제어권을 넘긴다. + +4. 현재 실행 중이던 프로세서의 상태 및 정보를 PCB(process control block)에 저장한다. 그리고 PC(program counter)에는 다음에 실행할 명령어의 주소를 저장한다. + +5. 시스템 콜 루틴에 해당하는 곳으로 점프하고, 시스템 콜 테이블을 참조하여 파일 읽기에 해당하는 시스템 콜을 실행한다. + +6. 해당 루틴을 끝내면, mode bit를 1로 바꾸고 PCB에 저장했던 상태들과 PC를 복원시킨다. + +7. PC에 저장된 주소(=마지막으로 실행했던 명령어의 다음)로 점프하여 계속 실행한다. + + + +----------------------- + + + +> **mutiprograming and multitasking** +> +> 한 작업이 다른 일을 하고 있다면(IO작업) cpu의 사용률을 높이기 위해 다른 작업에 cpu를 사용할 수 있다 +> +> +> +> time sharing system : cpu가 특정 시간에만 사용 할 수 있다. 많은 task들이 공유 할수 있는 구조 ==> multiprogramming과의 차이점이다 \ No newline at end of file diff --git a/_posts/2022-07-10-Process.md b/_posts/2022-07-10-Process.md new file mode 100644 index 000000000000..7de9096ee51e --- /dev/null +++ b/_posts/2022-07-10-Process.md @@ -0,0 +1,343 @@ +--- +layout: single +title: "Process" +categories: [Operating System,OS,Process,Context switching,Process control block, PCB,Cooperated process,System call] +tag : [Operating System,OS,Process,system call] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + + + +### 시작하며 + +프로세스 관련 내용 정리한 pdf는 아래 링크를 통해 확인 할 수 있습니다. + +Process_PDF + + + +----------------------------------------------- + + + +### 내용 정리 + + + +> **process** +> +> 정의 : 실행중인 혹은 실행을 위해 주기억장치 상에 적재된 프로그램 메모리에 load된 순간부터 프로세스가 된다 +> +> 프로세스는 하나의 프로그램을 여러번 돌리기 위해서 필요하다 +> +> +> +> * data,bss,stack,heap,code 영역으로 나뉜다 +> +> * IO 시간이 cpu 시간보다 느리기 때문에 scheduling이 필요하다 +> +> * active는 passive가 있어야 돌아갈수있다 -> 프로세스가 아니라 프로그램이 돈다는 표현 하는 이유 +> +> * 프로그램은 여러개의 프로세스로 이루어져 있다고 생각해도 큰 문제는 없다 +> +> * 프로그램은 passive +> +> * 프로세스는 active이다 +> +> +> +> * ![process_state](C:\github_blog\meang123.github.io\images\2022-07-10-Process\process_state.png) +> +> New,ready,running, wating,terminated 단계로 프로세스 상태를 나타낼수 있다 +> +> +> +> * **New** +> +> * 프로세스가 처음 만들어질때의 상태 +> * 해당 프로세스의 PCB가 커널 내부에 생성 +> * +> +> * **ready** +> +> * +> +> * **running** +> +> * running -> waiting +> +> * I/O 때문이다 +> * User의 key 입력을 받아야하는 상황일때 +> * Output screen에 출력해야하는 경우 +> * OS는 ready상태와 waitting상태를 구분해야한다 +> * I/O 이외에도 Hardware Event , OS operation(우선순위문제등)에 의해 watting으로 가기도 한다 +> +> - **Wait for user to type the next key (다음 키 칠때까지 기달)** +> - **Wait for outptut to appear on the screen (display 시킬 때까지 기달)** +> - **Program tried to read a file (OS가 어떤 블록을 읽고 실제로 메모리에서 요청된 정보를 읽을 때까지)** +> - **Netscape tries to follw a link (URL) - 웹사이트에서 주는 정보를 다 받을 때까지** +> - +> +> * running -> ready +> +> * process가 cpu 시간을 다 썼을 경우 -> time sharing system +> +> * priority scheduling 따라 interrupt에 의해 ready상태로 갈수도 있다 -> **context switching** 일어난거라고 생각해도 됨 +> +> +> +> * **waiting(block)** +> +> * critical section을 기다릴때도 waiting 상태를 사용한다 +> +> * **terminated** +> +> * 정상 종료,에러 났을때, 부적절한 명령어 실행 되었을때, 메모리 부족 할때, IO failuer일때 발생한다 +> * PCB도 제거 된다 + + + + + +## **Process control block(PCB)** + + + +실행 프로세스 정보와 상태를 저장해 놓은 자료 구조 + +bss 영역에 저장 된다 + + + +>* **PID (process id number)** +> +> +> +>* **Userid of owner** +> +>* - 계정 정보 (자원 사용시간등을 관리) +> +> +> +>- **Memory space** +> +>- - 메모리 관리 정보(page table,segment table) +> +> +> +>- **Pc,sp, register** **정보들** +> +>- - CPU 레지스터에 있던 그 당시 상태 값 +> +> +> +>- **Process State** +> +>- - new,ready,watting…. +> +> +> +>- **CPU scheduling information** +> +>- - 우선순위등과 같은 스케줄링 관련 정보들 -> 우선순위 때문에 스케줄러가 관여한다 +> +> +> +>- **I/O state** +> +>- - 입출력 상태 정보 (할당 받은 입출력 장치, 파일등에 대한 정보등 ) +> +> +> +>- **context saving** **영역 저장** + + + + + +## **System call** + + + +> 응용 프로그램의 요청에 따라 커널에 접근하기 위한 interface이다 +> +> 운영체제 서비스 접근하기 위한 수단이다. system call을 통해 커널 자원 사용 요청한다. +> +> +> +> user mode에서 운영체제 서비스인 메모리, 하드웨어 자원을 사용할때 OS 서비스를 사용해야한다 +> +> +> +> * 종류 +> +> * **Process control** +> +> * load,execute,wait for time, abort .... +> +> +> +> * **File manipulation** +> +> * create,delete,open,write,read,close등의 연산 할때 +> +> +> +> * **Device manipulation** +> +> * request device, release device +> +> * logically atteach or detach device +> +> +> +> * **information mainternace** +> +> * 시간, 날짜 정보 유지 하는것도 system call이다 +> +> +> +> * **Communications** +> +> * 프로세스간에 통신 + + + + + +## **context switch** + + + + + +> - 현재 프로세스의 상태를 저장하고 다른 프로세스의 상태를 읽어 오는 과정이다 +> - switching동안에는 아무것도 못한다 --> pure overhead +> +> + contex는 현재 process의 PCB 내용이다 +> +> - 커널에 의해 발생하기 때문에 overhead가 있다 +> +> - interrupt가 걸리면 다른 프로세스 먼저 실행한다 +> +> - system call과 context switching은 다른 개념이다 + + + +**사용하는 이유** + +* overhead이지만 전체적으로 보면 이득이다 + +* io시간 같이 오래 걸리는 작업을 기다리기보다 다른 process일을 하는게 전제척으로 보면 오히려 overhead가 작다 + + + +**많이 자주 사용하지 않는 이유** + +- 순수한 overhead가 발생하기 때문이다 +- 자주 문맥 전환하면 오히려 실행 시간 보다 스위칭에 더 많은 시간을 쓰게 된다 + + + +**프로세스간 context switching은 메모리가 다르기 때문에 메모리 관련 작업을 추가로 해야한다 ==> thread간 문맥전화이 더 가벼운 이유이다 tread간에는 메모리 관련 작업 하지 않는다 ** + + + + + +-------- + + + +### **Inter Process Communication (IPC)** + + + +프로세스 사이의 통신이다. messaging passing model과 shared memory model이 있다 + + + +> 프로세스가 독립적이라면 서로 통신 할 수 없다. OS가 자신 이외에 할당된 메몰이 공간 영역에 접근하는것을 허용하지 않기 때문이다. +> +> 그러나 협력 프로세스는 다른 프로세스의 실행에 영향줄수있다. 하나의 일을 끝내기 위해 여러개의 프로세스가 협력한다 +> +> +> +> **협력 프로세스 장점** +> +> * imformation sharing +> * computation speed up +> * modularity +> * convenience + + + + + +## shared memory model + + + +- 데이터를 공유 하는 주기억 장치 상의 공유 영역을 만들어서 협력 프로세스들 간에 사용하도록 하는 모델 + +- 처음 공유 메모리를 설정하는 시스템 호출을 제외하면 이후 공유 메모리 사용 접근에는 커널의 개입 없이 자유롭게 사용가능하다 + +- 생산자-소비자 문제에 대한 해법의 일종이다 + - 생산된 데이터만 보관하는 버퍼를 만들고 그 버퍼에 내용을 소비자가 사용하는 방식으로 해결 하는 방법이 있다 + - 이때 버퍼 구현은 Bounded buffer, Unbounded buffer 방식이 있다 ---- 자세한 내용은 위의 pdf에서 확인 + + + + + +shared memory model은 프로세스 안에 스레드가 사용한다. 같은 주소에서 공유 메모리 사용할때 우위가 있다 + + + +---------------- + + + +## message Passing model + + + +* 프로세스간 메시지의 형태로 데이터를 주고 받는 형태이다 -> 같은 주소 공유하지 않고 동기화하는 방법이다 + +* distributed system (client-server model)에서 유용하다 + * 통신하고자 하는 프로세스가 네트워크로 연결된 다른 컴퓨터에 있을때 유용한 방식이다 +* 직접/간접 통신 방법이 있다 - > pdf 참고 +* 두 프로세스 간에 통신을 하기 위해서는 반드시 두 프로세스 간에 공유되는 메일박스가 있어야한다 + * mail box는 kernel안에 위치한 버퍼이다. 즉 커널을 통해 프로세스 통신을 하는 모델이다 + * 각각의 mail box는 고유 식별자를 가진다 + + + +![IPC@](C:\github_blog\meang123.github.io\images\2022-07-10-Process\IPC@.png) + + + +* send, recive operation이 있다 + +* 동기화 , 비동기화 방식으로 send, receive operation 방법이 있다 --> PDF 내용 확인 + + + + + + + + + + + + + + + + + diff --git a/_posts/2022-07-13-Treads.md b/_posts/2022-07-13-Treads.md new file mode 100644 index 000000000000..68b4efb3defd --- /dev/null +++ b/_posts/2022-07-13-Treads.md @@ -0,0 +1,186 @@ +--- +layout: single +title: "Threads" +categories: [Operating System,OS,Thread,C++,multi threads, fork,exec,system call] +tag : [Operating System,OS,Thread,multi thread,user level,kernel level] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + + + +### 시작 하며 + +스레드에 관한 내용을 포스팅 할 것 입니다. 밑에 pdf 링크에서 스레드에 관하여 정리한 내용을 확인 할 수 있습니다. + + + +Thread_PDF + + + +이번 포스팅에서는 pdf에 내용을 보충하여 정리 하겠습니다. + + + +------------------- + + + +### 내용 정리 + + + +## **Thread** + + + +* 한 프로세스 내에서 실행 흐름의 단위 혹은 cpu가 처리할수있는 작업의 최소 단위 + +* unit of scheduling + + + +> 등장 배경 +> +> 프로세스 기반일때라는 가정을 하면 두가지 이상의 일을 처리하기 위해 context switching으로 처리하면 overhead가 상당히 크다 +> +> 프로세스가 독립적으로 분리 되어있기 때문이다. 그래서 나온 개념이 thread이다. 모든것을 독립해서 처리 할 필요가 없다는 것이다. +> +> 스레드는 프로세스 안에서 공유 하는 상태 정보들(code, data, file..)이 있기 때문에 프로세스가 context switching하는것 보다 효율이 좋다 왜냐하면 **메모리 접근을 할 필요가 없기 때문이다** + + + + + +![제목 없는 그림](C:\github_blog\meang123.github.io\images\2022-07-13-Treads\제목 없는 그림.png) + + + +- Stack, TID, PC, Register set을 thread마다 할당한다. +- 코드 영역, heap영역, data영역을 공유한다 + + + + + +![다운로드](C:\github_blog\meang123.github.io\images\2022-07-13-Treads\다운로드.png) + + + + + +multi thread가 프로세스보다 가지는 장점도 있지만 -- pdf 참고 + +하나의 스레드에 문제가 발생하면 프로그램 전체에 영향을 줄수있다는 단점도 있다. ------> 동기화 문제에 신경을 써야한다. + + + +> **프로세스와 스레드의 차이 ** +> +> 지금까지 프로세스 상태 및 문맥 전환 내용은 스레드에 관한 내용이다. +> +> 같은 프로세스 안에 속해 있는 스레드 끼리 context switching 일어나면 메모리 접근이 없으니까 속도가 빠르다 +> +> 하지만 다른 프로세스 안에 있는 스레드와 context switching 일어나면 기존의 프로세스끼리의 문맥 전환과 같다 즉 메모리 접근을 한다 +> +> 즉 근본적인 차이점은 메모리를 공유하냐 안하냐의 차이라고 이해하면 될것 같다 + + + + + +----------------- + + + +## **Multi Thread Model** + + + +- **Kernel level thread** + + - > 커널이 스레드를 생성해주는 모델이다 -> system call을 통해 스레드 생성 + > + > 즉 스케줄러와 스케줄링에 필요한 스레드 정보가 커널 영역에 존재하는 모델이다 + > + > + +- **User level thread** + + - > 커널에 의존적이지 않은 형태로 스레드 기능을 제공하는 라이브러리를 활용한 모델이다 + > + > 유저 영역에서 실행 되기 때문에 커널은 스레드 존재를 확인하지 못한다 + + + +user mode와 kernel mode는 서로 독립적인 영역이다. user mode에서 동작시 kernel mode 영역으로 접근할수 없다. kernel mode일때는 모든 영역 접근 허용된다. + +관련 내용은 3가지 모델과 함께 pdf에 더 자세하게 나와있다 + + + +스레드는 메모리를 공유하기 때문에 동기화하는 작업이 중요하다 + + + +----------------- + + + +## fork() exec() system call + + + +fork를 통해 부모와 똑같은 자식 프로세스 만든다 pid만 다르다 exec의 argument를 통해 완전히 다른 프로세스로 대체 된다 + +자세한 내용은 프로세스 포스팅에 pdf 자료를 보면 될것 같다 + + + +하나의 프로세스 대해서 fork할때는 자식 프로세스 만든다는 개념인데 만약에 2개의 프로세스가 존재 하는데 fork를 하면 2개에 대한 자식 프로세스가 만들어진다 즉 fork를 한번에 연속적으로 사용하면 개수가 늘어난다 + + + +UNIX system에서는 fork의 종류를 두가지로 분류 할수있다 + + + +* 모든 스레드에 대해서 fork +* thread that invoked 에 대해서만 fork 즉 특정 프로세스에 대해서만 fork처리 한다는 의미 이다 + + + +application에 따라 두가지 방법중에 언제 쓰는지 결정 할수있다 + + + +**시나리오 1** + +> fork후 바로 exec하는 경우 2번째 방법으로 fork 하는 방식이다 +> +> 왜냐하면 모든 스레드에 대해서 fork했다고 하고 여러개의 자식 프로세스 만들어도 exec하면 하나의 새로운 프로세스로 대체 되기 때문이다 즉 모든 스레드에 대해 자식 프로세스 만들 필요가 없는 상황인것이다 + + + +**시나리오 2** + +>forking이후 exec()하지 않는 경우 첫번째 방법으로 fork하는 방식 +> +> + + + + + + + + + + + + + diff --git a/_posts/2022-07-14-Scheduling.md b/_posts/2022-07-14-Scheduling.md new file mode 100644 index 000000000000..578e0e3e6279 --- /dev/null +++ b/_posts/2022-07-14-Scheduling.md @@ -0,0 +1,110 @@ +--- +layout: single +title: "Scheduling" +categories: [Operating System,OS,Scheduling,priority inversion,priority scheduling,Round Robine scheduling] +tag : [Operating System,OS,CPU scheduling] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + + + +### 시작하며 + +이번 포스팅에서는 스케쥴링 알고리즘에 관하여 포스팅 합니다 관련 내용은 아래 pdf에서 확인 할수있습니다. + + + +(전반적인 내용이 pdf와 겹치는 내용의 반복일것 같아서 따로 내용 정리는 생략 했습니다) + + + +추가로 포스팅하는 내용은 priority inversion 우선순위 역전에 관하여 추가 하고 포스팅 마무리 하겠습니다. + + + +Scheduling_PDF + + + +-------- + + + + +### 추가 보완 내용 + + + +## **priority inversion** + + + +> **우선 순위 역전 문제** +> +> - 우선 순위가 높은 태스크가 READY상태 (실행 가능)로 바뀌었지만 더 낮은 우선순위의 태스크가 CPU를 점유하고 있어서 실행되지 못하는 상태 +> +> +> +> > **발생 원인 :** 스케쥴링과 동기화 사이의 상호 작용 결과로 발생한다 +> > +> > 스케쥴링 규칙에서 실행되어야 하는 스레드와 동기화에서 실행되어야하는 스레드가 서로 다른 경우 +> > +> > 결과적으로 두 스레드의 우선순위가 역전되어 나타난다 +> +> +> +> 1. 비선점 스케쥴링이 낮은 우선순위의 task가 자원을 점유 [no preemption scheduling] +> 2. RTOS서의 multex를 위한 세마포어를 이용 +> 3. 공유자원의 장기 소유 +> 4. 자원점유후 release시 낮은 우선순위의 태스크가 자원을 점유하는 경우 +> +> +> +> +> +> RTOS 개발시 태스크간에 우선순위를 예상하여 inversion 일어나지 않도록 해야한다 +> +> +> +> +> +> +> +> **해결 기법** +> +> +> +> 1. **Priority inheritance protocol (계승 프로토콜)** +> +> +> +> * 우선순위가 낮은 프로세스가 세마포어를 소유하고 실행중일때 우선순위가 높은 +> +> 프로세스가 실행되었을때 세마포어가 없다면 실행이 안되는데 우선순위가 낮은 프로세스를 +> +> 우선순위가 높은 프로세스만큼 우선순위를 일시적으로 높여주는 것이다 +> +> +> +> * 높은 우선순위의 프로세스를 블록시키고 있는 프로세스(하)는 높은 우선순위 프로세스의 우선순위를 계승하는것이다. 이 프로세스(하)가 세마포어를 반환하면 원래의 우선순위로 되돌아간다. +> +> +> +> +> +> ![ffff](C:\github_blog\meang123.github.io\images\2022-07-14-Scheduling\ffff.png) +> +> +> +> ![dfg](C:\github_blog\meang123.github.io\images\2022-07-14-Scheduling\dfg.png) +> +> +> +> +> +> 2. **Priority ceiling protocol (상한 프로토콜)** +> * 주로 1번 방법으로 해결하는것 같아서 따로 정리는 안했습니다. 필요해지면 다시 업데이트 하는 방식으로 하겠습니다. 키워드는 있으니까 궁금하신 분은 검색 하시면 될것 같아요 diff --git a/_posts/2022-07-15-Threads_c++.md b/_posts/2022-07-15-Threads_c++.md new file mode 100644 index 000000000000..e169a1cf600d --- /dev/null +++ b/_posts/2022-07-15-Threads_c++.md @@ -0,0 +1,311 @@ +--- +layout: single +title: "1편 Threads c++" +categories: [Operating System,OS,Multi Thread,C++,cppreference,join,jthread] +tag : [Operating System,OS,Thread,join,jthread] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + + + +### 시작하며 + +멀티 스레드 c++ 1편은 스레드에 관한 내용을 포스팅 하겠습니다. +intro에서도 언급 했듯이 **"코드없는 프로그래밍"** 유튜브 내용을 참고 하면서 작성 했습니다. 추가로 cpprefernce 내용도 함께 참고해서 포스팅 하였습니다. + +멀티 스레드의 문제는 동기화 입니다. 동기화에 대해 이해도가 있다면 이해하기 쉬워 질것입니다. + + + +--------------------------- + + + +c++에서 thread는 object로 만들어지고 object가 stack이나 heap영역에 스레드 생성하는 방식이다. + + + +## constructor + + + +아래 코드는 cpprefernce thread constructor부분 코드 입니다. + +```c++ +#include +#include +#include +#include + +void f1(int n) //call by value +{ + for (int i = 0; i < 5; ++i) { + std::cout << "Thread 1 executing\n"; + ++n; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); //10milsecond 만큼 기다립니다 + } +} + +void f2(int& n) //call by reference +{ + for (int i = 0; i < 5; ++i) { + std::cout << "Thread 2 executing\n"; + ++n; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } +} + +class foo +{ +public: + void bar() + { + for (int i = 0; i < 5; ++i) { + std::cout << "Thread 3 executing\n"; + ++n; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + } + int n = 0; +}; + +class baz +{ +public: + void operator()() + { + for (int i = 0; i < 5; ++i) { + std::cout << "Thread 4 executing\n"; + ++n; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + } + int n = 0; +}; + +int main() +{ + int n = 0; + foo f; + baz b; + std::thread t1; // t1 is not a thread + std::thread t2(f1, n + 1); // pass by value + // 함수와 인자 전달 하는 방법 + + std::thread t3(f2, std::ref(n)); // pass by reference refernce는 ref()를 통해 wrapping해야한다 + + std::thread t4(std::move(t3)); // t4 is now running f2(). t3 is no longer a thread + // copy constroctor안됨 R value 또는 move constroctor 여야한다 + + std::thread t5(&foo::bar, &f); // t5 runs foo::bar() on object f + std::thread t6(b); // t6 runs baz::operator() on a copy of object b + t2.join(); + t4.join(); + t5.join(); + t6.join(); + std::cout << "Final value of n is " << n << '\n'; + std::cout << "Final value of f.n (foo::n) is " << f.n << '\n'; + std::cout << "Final value of b.n (baz::n) is " << b.n << '\n'; +} +``` + + + +std::thread (fn)하면 main에서 만들어진 thread object는 stack 영역에 fn 함수를 가리키고 join()을 통해 fn 스레드가 실행 한다 + + + +* copy constroctor는 안되고 move constroctor로 스레드 객체를 넘길수있다 즉 R value +* 함수와 함수 인자를 thread 통해 전달하는 방식은 위의 코드 참고 +* reference로 전달할때는 std::ref()으로 wrapping 해주어야 한다 + + + + + +## destroctor + +thread 종료 시키는 terminator가 작동하면서 종료 된다 + +cf : jthread 같은 경우 destroctor에 join과 stop token이 있어서 자동으로 가능 + + + + + +## join + +현재 thread를 block 하고 실행이 끝날때까지 기다린다 -> 우선 순위 스케쥴링 방식이 아니다 ,no context switching + + + +join의 문제는 생성한 스레드가 join을 하지 않거나 여러번 중첩 사용했을때 발생한다. main에서 만들어진 thread object는 연결 되어있는데 join 하지 않으면 main에 있는 thread object가 종료가 되기 때문에 문제가 발생한다. + +이런 문제를 c++20 이전에는 detach를 통해 분리 할수있었다. 근데 분리하는 구조는 좋은 구조는 아니다. 그래서 c++ 20에서는 jthread를 지원한다. jthread는 아래에서 더 자세히 알아본다. + + + +thread object는 stack영역, heap 영역에 생성 할 수 있다. 아래 코드는 heap영역에 thread생성 한 것이다. + + + +```c++ +#include +#include +#include +#include + +void fn() +{ + cout << this_thread::get_id() << endl; //thread id +} + +int main() +{ + cout << "my computer is supported " << thread::hardware_concurrency() << endl; + + //thread vector 만들어서 heap영역에 스레드 생성 할수도 있다 + + //thread는 object이다 + vector threads; + for (int i = 0; i < 10; i++) + { + threads.emplace_back(thread(fn)); + + } + + for (auto& thread : threads) + { + thread.join(); + } + + + + return 0; +} + +``` + + + +----------------- + + + +멀티 스레드는 공유 자원을 쓰기 때문에 동기화 하는 문제가 중요하다. 아래의 코드를 보면 스레드를 생성하면 join을 해주어서 동기화 해주는것을 알수있다. 만약 이런 동기화 작업이 없다면 올바른 결과가 나오지 않을것이다. -- 사실 여기까지만 살펴보면 순차적 프로그램이랑 별 차이가 없다 후에 동기화 부분에 대해서 보고 나면 이해하기 쉬어질것으로 생각 된다.-- + + + +그리고 Operating system 포스팅에서도 스레드 동기화 내용이 있으니 이 부분을 먼저 보고 오면 더 도움이 될것 같다 + + + +```c++ +#include +#include +#include +#include + +void fn(int &a) +{ + this_thread::sleep_for(chrono::seconds(1s)); + cout << a << endl; +} +void threadCaller(thread& t) +{ + int num = 42; + t = thread(fn, std::ref(num)); + t.join(); //동기화 중요하다 +} +int main() +{ + thread t1; + threadCaller(t1); + t1.join(); + + + return 0; +} + +``` + + + + + +---------------------- + + + +> **thread_local keyword** +> +> 함수의 지역 변수와 같은 맥락이다 +> +> 만들어진 스레드에 대해서 독립적으로 가지는 keyword이다. +> +> 즉 생성된 각 스레드마다 독립적인 공간을 가진다고 이해 하면 될것 같다 ---> 관련내용이 더 필요하다면 cppreference thread_local 부분을 인터넷에서 참고 하면 될것 같다 + + + + + +## Jthread + +이전에 join에서 발견된 문제를 보완해주는 내용이다. c++20 버전부터 지원을 한다 + + + +* rejoin in 한다고 보면 된다. 즉 join, detach하지 않아도 에러 없이 종료 할수 있다. + +* stop token 있다 + * 무한히 기다려 주지 않는다는 의미이다. + * 일정 시간이 지나면 자동으로 terminate한다 + * **stop token에 관한 내용은 cppreference jthread stop_token에 관련 내용이 있다. 필요하면 참고 하자** + + + +```c++ +#include +#include +#include +#include + +using namespace std; + +void fn() +{ + cout << "this is fn function " << endl; +} +int main() +{ + jthread t1(fn); + + + return 0; +} +``` + + + +join함수가 없어도 정상 작동을 한다. 그리고 fn에서 무한 루프가 있다고 해도 일정 시간이 지나면 terminate 된다 + + + +-------------- + + + +## Data Race + +두개 이상의 스레드가 공유 자원을 사용하고 있으면서 발생하는 문제 + +결국 동기화 관련 문제이다 이 문제를 해결하기 위해서는 동기화에 대한 내용을 알아야 한다 다음 포스팅에서 이어서 하겠다. + + + + + diff --git a/_posts/2022-07-15-multi_thread_intro.md b/_posts/2022-07-15-multi_thread_intro.md new file mode 100644 index 000000000000..cff0857a2b4d --- /dev/null +++ b/_posts/2022-07-15-multi_thread_intro.md @@ -0,0 +1,24 @@ +--- +layout: single +title: "multi threads c++" +categories: [Operating System,OS,Multi Thread,C++,introduction,intro] +tag : [Operating System,OS,Thread,introduction] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +### 시작 하며 + +멀티 스레드 관련 개념을 코딩을 통해 더 자세히 다루기 위해 이번 포스팅 부터 시작하려 합니다. +thread,semaphore,lock, mutex등 OS에서 배운 내용을 직접 코딩 해보면서 한층 더 이해하는 시간이 되었으면 좋겠습니다. + +이번 포스팅 부터 진행하는 내용은 코드없는 프로그래밍 유트브를 따라해보면서 정리한 내용입니다. 추가로 제가 공부한 내용까지 포스팅을 할것이지만 저작권은 **"코드없는 프로그래밍 유튜브에 있습니다."** + +아래 링크를 통해 관련 영상 볼 수 있습니다. + +[코드없는 프로그래밍](https://www.youtube.com/channel/UCHcG02L6TSS-StkSbqVy6Fg) +{: .notice--danger} + diff --git a/_posts/2022-07-21-multi_thread_syn.md b/_posts/2022-07-21-multi_thread_syn.md new file mode 100644 index 000000000000..45f3fba8f2b9 --- /dev/null +++ b/_posts/2022-07-21-multi_thread_syn.md @@ -0,0 +1,765 @@ +--- +layout: single +title: "multi thread Synchronize with c++" +categories: [Operating System,multi thread,process,synchronize,semaphore,mutex,critical section,c++,C++,Lock] +tag : [Operating System,OS,process,synchronize,Mutex,Critical Section,Critical Section Problem,Semaphore,c++,lock] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + + + +### 시작하며 + +멀티 스레드 챕터 시리즈 이어나가고 있습니다. 이번 포스팅에서는 지난 포스팅에서 문제가 되었던 data race관련 문제에 관한 이야기 입니다. +동기화에 대한 내용을 코드와 함께 정리 하는 포스팅입니다. + +또한 OS 부분에서 프로세스/ 스레드 동기화 대한 내용 포스팅 한적 있는데 먼저 보고 오시면 이해하는데 더 도움이 될것 같습니다. + +그래서 개념에 대한 이야기 보다는 어떻게 코드로 구현이 되는지에 초점을 두고 진행하겠습니다. + + + +------------- + + + +## Mutex + + + +lock , unlock 개념 #include mutex 해야 사용 할수 있다 + + + +mutex로 보호하는 critical section은 최소화가 되어야 의미가 있다 만약 공유 되는 자원이 많아지면 즉 critical section 커지면 하나의 스레드만 계속 사용되기 때문에 비효율적이 된다 즉 핵심은 최소화 해야한다. + + + +**그리고 흔히 mutex에서 착각하는게 있는데 lock을 얻지 못해 block상태에 갔다고 해서 먼저 온 스레드가 lock을 먼저 획득한다는 보장은 없다** + +스케쥴링 방식 ,OS 구현 방식에 따라 달라진다 + + + +```c++ +/* +mutex 관련 코드 + +임계구역은 최대한 적게 해야한다 +lock을 했을때 성능이 어느 정도 나오는지 검사하는 코드 + +*/ + +#include +#include +#include +#include +#include + +using namespace std; +using namespace std::chrono; + +const int MAX_THREAD = 64; + +volatile int sum; //compile 최적화 하지 말아라 + +mutex ml; +void thread_func() +{ + + //10만번 까지 더하기 + for (int i = 0; i < 100000; i++) + { + + ml.lock(); + sum++; //critical section + ml.unlock(); + + + } +} + +int main() +{ + vector threads; + + for (int i = 1; i <= MAX_THREAD; i *= 2) + { + sum = 0; + threads.clear(); + + auto start = high_resolution_clock::now(); + + for (auto j = 0; j < i; j++) + { + threads.emplace_back(thread(thread_func)); + + } + + for (auto& tmp : threads) + { + tmp.join(); + } + + auto duration = high_resolution_clock::now() - start; + + cout << i << " threads" << " sum = " << sum; + cout << " Duration = " << duration_cast(duration).count() << " milliseonds\n"; + } + return 0; +} +``` + + + +![화면 캡처 2022-07-21 201055](C:\github_blog\meang123.github.io\images\2022-07-21-multi_thread_syn\화면 캡처 2022-07-21 201055.png) + + + +위의 코드는 lock을 이용해서 성능을 체크 해보는 코드 이다. 결과를 보면 성능이 느려졌다. + +왜냐하면 lock은 오버헤드이기 때문에 부하가 발생한다. + + + + + +**lock의 개수를 최소화하고 lock으로 보호받는 구간인 cs를 최소화 해야한다. ** + +```c++ +/* +mutex 관련 코드 + +임계구역은 최대한 적게 해야한다 +lock을 했을때 성능이 어느 정도 나오는지 검사하는 코드 + +*/ + +#include +#include +#include +#include +#include + +using namespace std; +using namespace std::chrono; + +const int MAX_THREAD = 64; + +volatile int sum; //compile 최적화 하지 말아라 + +mutex ml; +void thread_func() +{ + volatile int localSum = 0; + + + //백만번 까지 더하기 + for (int i = 0; i < 10000000; i++) + { + localSum++; + } + + + + ml.lock(); + sum = localSum; //cs영역 + ml.unlock(); + +} + +int main() +{ + vector threads; + + for (int i = 1; i <= MAX_THREAD; i *= 2) + { + sum = 0; + threads.clear(); + + auto start = high_resolution_clock::now(); + + for (auto j = 0; j < i; j++) + { + threads.emplace_back(thread(thread_func)); + + } + + for (auto& tmp : threads) + { + tmp.join(); + } + + auto duration = high_resolution_clock::now() - start; + + cout << i << " threads" << " sum = " << sum; + cout << " Duration = " << duration_cast(duration).count() << " milliseonds\n"; + } + return 0; +} +``` + + + +![화면 캡처 2022-07-21 203944](C:\github_blog\meang123.github.io\images\2022-07-21-multi_thread_syn\화면 캡처 2022-07-21 203944.png) + + + +10만에서 100만으로 늘렸는데도 성능이 더 좋아진 것을 알수있다 + + + + + +mutex는 copy와 move가 안된다. lock과 unlock은 mutex의 개념과 똑같이 구현되어있어서 위의 코드로 생략한다 + + + +--------- + + + +### lock_guard + +사용 할때 lock과 unlock을 사용하면 예외가 발생하여 비정상적으로 종료 되었거나 아니면 문제가 생겼을때 unlock이 되지 않아 문제가 발생할수있다. 그래서 lock_guard를 사용하는게 더 안정적이다. lock_guard를 사용하면 아래 오는 코드는 자동으로 Critical section으로 인식 되고 scope가 끝나면 자동으로 unlock을 호출한다 -> scope단위로 unlock해주므로 더 안전하다 하자만 사용하는데 제한적이다. + + + +```c++ + const std::lock_guard lock(g_i_mutex); +``` + + + +----------- + + + +### unique_lock + +lock_guard와 같은 기능을 하지만 더 많은 기능을 제공한다 move도 가능하고 여러 메서드 사용 가능하다 + +move가 가능하다는것은 mutex lock을 resuorece로 관리 할수있다는 의미이다. 안전하게 관리 할수있다 + +lock_guard가 제한이 있지만 가볍고 간단한 설계 가능하다. 만약 lock 기능이 더 필요할때 사용하면 될것 같다 + + + +----------- + + + +### scoped_lock + +여러개의 mutex lock을 사용하다가 순서가 맞지 않으면 deadlock에 쉽게 빠지게 된다. deadlock을 피하기위해서는 동기화를 해주어야하는데 scpoped_lock을 사용하면 순서를 맞추어주어서 deadlock을 회피 할수있다. + +동작 범위는 lock_guard와 마찬가지로 scope단위이다 + + + +```c++ +/* + scoped_lock 관련 예제 코드 + 노코프 유튜브 내용속 코드 입니다. + + cppreference scpod_lock 예제 코드도 참고 하시면 좋을것 같아요 + + + +*/ + +#include +#include +#include +#include + +using namespace std; + +mutex mtxA, mtxB; //mutex 2개 생성 + +void a_to_b() +{ + const scoped_lock lck(mtxA, mtxB); + + this_thread::sleep_for(chrono::seconds(1)); +} +void b_to_a() +{ + const scoped_lock lck(mtxA, mtxB); + + this_thread::sleep_for(chrono::seconds(1)); +} +int main() +{ + thread t1(a_to_b); + thread t2(b_to_a); + + t1.join(); + t2.join(); + + cout << "bye" << endl; +} +``` + + + +실행 결과 bye의 결과가 나오는것을 알수있습니다. + + + +-------------------- + + + +### shared_mutex + +여러개의 mutex가 critical section에 들어갈수있는 구조 들어간 mutex가 다 빠져 나오면 lock반납한다 + + + +두가지 접근 단계가 있다. + + + +- shared +- exclusive + + + +shared_mutex가 필요한 이유는 read/write할때 필요하다. + +write작업은 하나의 스레드만 임계구역 들어가서 작동되어야한다 --exculsive + +하지만 read같은 경우는 shared되어도 문제 없다 어차피 read only file이니까 그러니 shared_mutex 사용하면 여러개의 스레드가 동시에 critical section에 진입 하여 read 작업을 할수있다 + + + + + +-------------------------------------- + + + +### 필요하면 찾아볼것들 + +* call_once + * call_once와 flag를 사용하면 여러 스레드 환경에서도 라도 함수가 한번만 실행된다 + + + +------- + + + +### scoped static init + +static같은 경우 stack 부분에서 static 영역에 따로 메모리 있다 그래서 여러 스레드가 접근해도 한번만 초기화 된다 + +singleton은 디자인패턴에서 나오는 부분인데 프로세스 전체에서 오직 하나의 object만 생성할수있는 패턴을 의미한다 + +scoped static을 활용한다면 한번만 초기화 시킬려고 mutex나 call_once 사용 안해도 된다 + + + +------- + + + +### condition variable + + + +프로세스/스레드 동기화 내용에 대한 포스터 내용을 보고 오면 더 쉽게 이해할수있습니다. + + + +- cpp에서는 을 include해주고 사용해야한다 + +- wait는 lock release 시키고 block한다 인자로는 unique_lock을 받는다 + + - template< class Predicate > + void wait( [std::unique_lock](http://en.cppreference.com/w/cpp/thread/unique_lock)<[std::mutex](http://en.cppreference.com/w/cpp/thread/mutex)>& lock, Predicate stop_waiting ); + + - ```c++ + while (!stop_waiting()) { + wait(lock); + } + ``` + + - 두번째 인자는 stop_waiting이 true일때만 빠져 나올수있는 옵션이다. + + + +- notify_one은 waiting queue에 있는 thread 하나만 wake up 하는것이다 signal과 같은 개념 + +- notify_all은 broadcast같은 개념이다. waiting queue에 있는 스레드 모두를 wake up 한다 + + + +```c++ +/* +condition varable 관련 예제 코드이다 + +wait(unique_lock,predict) -> cppreference condition varable wait 부분 보기 + +readyFlag는 shard variable로써 condition_variable의 동기화를 위해 넣어줌 + +모니터 생산자 소비자 문제에서 서로 순서(동기화)위해 공유 자원 버퍼가 비어있는지 채워져있는지로 조건을 확인했잖아 +그것과 같은 맥락이라고 보면 될것같다 + +*/ + +#include +#include +#include +#include + +using namespace std; + +bool readFlag = false; +mutex mtx; +condition_variable cv; + +void waitFn() +{ + cout << "wait"<<'\n'; + unique_lock lck(mtx); + + cv.wait(lck, [] {return readFlag; }); + + //critical section + lck.unlock(); + + cout << "re run " << '\n'; + +} +void signalFn() +{ + cout << "signal" << '\n'; + unique_lock lck(mtx); + readFlag = true; + lck.unlock(); + cv.notify_one(); +} +int main() +{ + thread waitT(waitFn); + thread signalT(signalFn); + + waitT.join(); + signalT.join(); + + return 0; +} +``` + + + + + +------------- + + + +### producer consumer problem + + + +buffer가 string vector stack구조라는거 빼면 나머지는 비슷하게 진행한다 + + + +```c++ +/*producer consumer problem + +*/ +#include +#include +#include +#include +#include +using namespace std; + +class strStack +{ + mutex mMtx; + vector mstr; + condition_variable mcv; + +public: + + //producer + void addStr(string s) + { + //제한된 buffer가 아니라 vector니까 따로 Full condition 확인할 필요는 없다 + { + lock_guard lck(mMtx); + mstr.emplace_back(move(s)); + } + mcv.notify_one(); + + + } + + //consumer + string getStr() + { + unique_lock lck(mMtx); + + while (mstr.empty()) + { + mcv.wait(lck); + } + string s = move(mstr.back()); + mstr.pop_back(); + lck.unlock(); + return s; + } + +}; + +int main() +{ + strStack str_stack; + + thread t1([&]() {str_stack.addStr("meangsungjoo"); }); + thread t2([&]() {str_stack.addStr("Have a GoodDay!!!"); }); + thread t3([&]() {cout< +#include +#include + +using namespace std; + +counting_semaphore<10> sp(0); //counting semaphore를 10으로 설정하고 초기값을 0으로 초기화하는 예제 + +void waitFn() +{ + cout << "waiting" << '\n'; + sp.acquire(); + cout << "rerun" << '\n'; +} +void signalFn() +{ + cout << "signal" << '\n'; + sp.release(); +} +int main() +{ + thread waitT(waitFn); + thread signalT(signalFn); + + waitT.join(); + signalT.join(); + + return 0; +} +``` + + + +----------------- + + + +### latch + +세마포어랑 비슷한데 count_down밖에 없다 + + + +스레드를 동기화 하기 위한 **downward couter**이다. counter 값은 생성 될때 정해지고 counter가 0이 될때까지 스레드들은 block 상태에 있다. counter를 reset하고 증가 시키는 방법은 없다 + +메서드에는 count_down()이 있다 latch의 counter--한다. wait()는 counter가 0이 아니면 block 0이라면 block 시키지 않고 진행한다 + +**count_down과 wait를 같이 쓰는 메서드 arrive_and_wait 메서드가 있다. ** + + + +주의 할점은 counter를 reset 할 수 없기 때문에 count_down을 초기값보다 많이해서 음수가된 상황에서 wait하면 block이 되는데 block을 풀 방법이 없다 + + + +```c++ +#include +#include +#include +#include +#include + +using namespace std; + +std::latch lt{3}; + +void fn() +{ + cout << "decrease counter" << '\n'; + cout << "wait" << '\n'; + + lt.arrive_and_wait(); //count_down() && wait() + + cout << "re run" << '\n'; + +} + +int main() +{ + vector threads; + + //초기화한 latch couter만큼 반복해야한다 + for (int i = 0; i < 3; i++) + { + this_thread::sleep_for(500ms); + threads.emplace_back(thread(fn)); + } + + for (auto& thread : threads) + { + thread.join(); + } + + return 0; +} +``` + + + + + +----------------------- + + + +### barrier + + + +latch와 비슷하다 여러 스레드가 차단 되었다가 어떠한 조건이 만족 되었을때 스레드가 진행한다 + +latch와 다르게 내부 counter가 reset이 되기 때문에 재사용이 가능하다 + +counter==0이 될때 동기화 되는 방식이 latch와 같은 맥락이다. + + + +barrier 역시 arrive, wait 지원하고 둘다 지원하는 arrive_and_wait()도 지원한다 + + + +스레드를 만들고 실행 하면 정해진 순서가 아니라 랜덤 순서로 스레드가 실행이 되는데 bariier를 사용하면 여러 스레드가 초기 설정한 barrier couter 만큼의 스레드가 도착할때까지 block 시켰다가 couter가 0이 되면 다시 reset하여 동기화(순서) 맞춘다 이때 couter가 0이 되고 다시 reset되는 단계를 **phase completion**이라고 한다 + + + +```c++ +#include +#include +#include +#include + +using namespace std; + +auto on_completion = []() noexcept { + cout << " phase completion" << endl; +}; + +//barrier 초기값 설정 및 phase completion 할때 작동하는 completionFunction도 인자로 넘겨 주었다 +std::barrier bar{ 3,on_completion }; + +void fn() +{ + cout << "1" << flush; + bar.arrive_and_wait(); + + cout << "2" << flush; + bar.arrive_and_wait(); + + cout << "3" << flush; + +} +int main() +{ + vector threads; + for (int i = 0; i < 3; i++) + { + threads.emplace_back(thread(fn)); + } + + for (auto& thread : threads) + { + thread.join(); + + } + return 0; +} +``` + + + + + +______________ + + + +### 마무리 + +이번 포스팅에서는 동기화에 관련 내용을 c++ 코드와 함께 알아보는 시간이었습니다. 다음 포스팅에서는 비동기 관련 포스팅으로 진행하겠습니다. + + + + + + + diff --git a/_posts/2022-07-27-asyn_thread.md b/_posts/2022-07-27-asyn_thread.md new file mode 100644 index 000000000000..d55c24488245 --- /dev/null +++ b/_posts/2022-07-27-asyn_thread.md @@ -0,0 +1,288 @@ +--- +layout: single +title: "multi thread Asynchronize with c++" +categories: [Operating System,multi thread,process,asynchronize,critical section,c++,C++,future,promise] +tag : [Operating System,OS,process,Asynchronize,Critical Section,c++,future,promise] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + + + +### 시작하며 + +task 기반의 비동기 처리에 관한 포스트 입니다. + +여러 작업들을 독립적으로 실행하도록 개발 하는것이 비동기 프로그래밍이다. 순서 상관없이 독립적으로 동시에 실행된다 + +------- + + + +## Promise + + + +* Header : include future + + + +future와 promise는 한쌍의 community channel이다 + +생성자 소비자 문제처럼 promise를 생성자 , future를 소비자의 역할로 생각 해도 된다 + + + +**간단한 구조 ** + + + +> future와 promise 관계에는 conditional variable, mutex, shared memory에 관한 내용이 전부 들어 있다 +> +> promise에서 set_value넣기 전까지 future는 get()을 통해 계속 wait 한다 +> +> promise가 set_value할때까지 wait하는게 아니라 그 시간동안 다른일 하도록 설정 할수 있다 future에 wait_for()를 통해 설정 시간마다 ready 상태인지 확인 할수있다. + + + +위의 구조 때문에 (heap에서 communication channel 만듬) 성능면에서는 좋지 않다 + + + +* constructor + * promise prom + + + +- promise 객체는 자신이 가지고 있는 future객체에 값을 넣어준다 이때 promise가 가지고 있는 future을 얻는 함수가 **get_future()**이다 + + + +- future에 넣는것은 set_value(data)로 한다 + + + +- copy안된다 move로 해야한다 + +- set_except통해 예외 전달 가능 + + + +--- + + + +## future + + + +async의 결과를 접근하기 위한 class template이다 + + + +* **get()**함수를 통해 promise가 set_value한 data 얻을수있다 이때 wait() 기능도 들어가있으므로 get()만 써도 된다 +* 단 두번 연속 호출하면 안된다 get을 하면 future 내에 전달 받은 객체가 이동되기 때문이다 +* future에 예외도 전달할수있다 + + + +```c++ +/* +* Async +* +promis future +*/ + +#include +#include +#include + +using namespace std; + +void fn(promise prom) +{ + std::this_thread::sleep_for(2s); + prom.set_value(42); +} +int main() +{ + promise prom; + future fut = prom.get_future(); + + thread t(fn, std::move(prom)); + + while (fut.wait_for(0.2s) != std::future_status::ready) + { + cout << "doing other work" << endl; + + } + const int num = fut.get(); + + cout << "num is " << num << endl; + + t.join(); + return 0; +} + +``` + + + +결과 : + +doing other work +doing other work +doing other work +doing other work +doing other work +doing other work +doing other work +doing other work +doing other work +num is 42 + + + +------------------------- + +**set_exception** 전달 예제 + +예외처리에서 예외문제가 나오면 그 예외를 set_exception을 통해 promise가 전달한다 + +* set_value와 같이 쓰면 안된다 + + + +```c++ +/* + +set_exception 예제 + + +*/ +#include +#include +#include +#include + +using namespace std; + +void fn(promise prom) +{ + using namespace std::chrono_literals; + this_thread::sleep_for(1s); + + try + { + throw std::runtime_error("Runtime Error"); + } + catch (...) + { + + prom.set_exception(current_exception()); + + } + + + +} +int main() +{ + promise prom; + future fut = prom.get_future(); + + thread t(fn, std::move(prom)); + + try + { + const int num = fut.get(); + cout << "num is " << num << endl; + } + catch (exception& e) + { + cout << "Exception is " << e.what() << endl; + } + + + t.join(); + return 0; +} +``` + +----------- + + + +## shared_future + + + +future는 copy가 안되기 때문에 promise가 set한 value를 여러개의 future가 참고 하지 못한다 이런 상황에서 copy가능한 shared_future를 사용할수있다 + +즉 copy가 필요할때 사용할수있다 + + + +**shared_future는 ready only(shared data가 바뀌지 않는다) 거나 External synchronization(동기화 문제) 제공된 경우에 사용 되어야한다** + + + +```c++ +/* +shared_future + +copyable + +*/ +#include +#include +#include +#include + + +using namespace std; + +void fn(shared_future fut) +{ + cout << "num " << fut.get() << endl; +} + +int main() +{ + using namespace chrono_literals; + + promise prms; + shared_future fut = prms.get_future(); + + vector threads; + for (int i = 0; i < 5; i++) + { + threads.emplace_back(fn, fut); + } + this_thread::sleep_for(1s); + + prms.set_value(42); + + + + return 0; +} +``` + + + +--------------- + + + +## async + + + + + + + diff --git a/_posts/2023-04-21-c++_intro.md b/_posts/2023-04-21-c++_intro.md new file mode 100644 index 000000000000..7602bd894544 --- /dev/null +++ b/_posts/2023-04-21-c++_intro.md @@ -0,0 +1,41 @@ +--- +layout: single +title: "c++ 문법 정리 및 유용한 기능 정리" +categories: [c++,introduction] +tag : [c++] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +### 들어가며 + +목적 : c++ 버전 open cv 사용 위해 c++다시 공부 할 필요가 있어서 문법도 다시 상기 하고 또한 c++을 최적화 하기 위해 어떻게 해야 하는지 공부 하기 위해 정리하는 내용을 가질려고 합니다. +제가 정리한 내용은 유튜버 2명을 보고 정리 한 내용입니다 모든 시리즈에 저작권은 2명의 유튜버에게 있습니다. + +c++가 처음이 아니기 때문에 모든것을 다 정리 하지는 않을 예정이고 상기하면 좋겠다는 내용을 주관적으로 결정해서 내용 정리 하고 있다는점 알고 계시면 좋을것 같습니다. + +[코드없는 프로그래밍](https://www.youtube.com/@user-pw9fm4gc7e) +
+
+ +[The Cherono](https://www.youtube.com/@TheCherno) + +
+
+ +-------- + +#### open cv C++ version 선택한 이유 + +[open cv c++ vs python 비교 영상](https://www.youtube.com/watch?v=MSrxJMq5qeg) + + +------------ + +#### 마무리 + +앞으로 관련 내용을 꾸준히 올릴 예정이니까 지켜봐 주세요! 또한 이상한 내용이 있다면 언제나 댓글로 알려주세요! +감사합니다!!!!! \ No newline at end of file diff --git a/_posts/2023-04-21-compile_linker.md b/_posts/2023-04-21-compile_linker.md new file mode 100644 index 000000000000..e66ab5df7aef --- /dev/null +++ b/_posts/2023-04-21-compile_linker.md @@ -0,0 +1,99 @@ +--- +layout: single +title: "c++ compiler_linker_build" +categories: [c++,compiler,linker,build,debug,header,C++,] +tag : [c++] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + + +!!!이 포스팅의 저작권은 c++ introduce에 설명이 되어 있음을 알립니다!!! + + + +### preprocessor, compiler, linker 큰 그림 + +**preprocessor** + + +미리 선언된 매크로 관한 내용 +__func__ +__LINE__ +__FILE__ +__DATE__ +__TIME__ + +__DATE__ ,__TIME__ 같은 경우는 컴파일 되었을때 시간이라서 언제 컴파일 했는지 알고 싶을때 유용하게 사용할수있다 + + +전처리기가 중복 선언안되게 visual studio 에서는 #pragma once를 지원 함 +그런데 다른 idle 에서는 #ifndef #ifdef 등을 통해 해야한다 + +compiler가 cpp파일을 obj파일로 만들고 linker가 lib와 함께 실행 파일 만드는 구조이다. + + +### static library + +.a , .lib가 static libaray 파일 확장자이다 static libaray 만든다는건 archive 파일 만드는것이다 -> 리눅스 환경에서 ar rs 명령어 사용해서 한번 해본적이 있다. visual studio환경에서는 파일 설정에서 static lib로 솔루션 안에 파일을 설정 할수있다. + + + + + +### dynamic libaray + +.dll, .so가 dynamic libaray 파일 확장자 이다. runtime에 bining된다 크게 loading time, run time이 있다. + +사용 이유 : 실행 파일 가지고 있고 업데이트 위해 dynamic lib만 바꿔주면 re build 하는 과정 없앨수있다 - static lib는 새로 생기면 다시 build해줘야 한다 + +cf : visual studio환경에서 솔루션 안에 여러개 파일 만든 상황 가정, 이때 파일끼리 상호 작용하려면 속성에서 절대 경로 설정을 하여야 한다. +해당 파일의 속성에서 c/c++에 general부분 가면 include directories 가 있다 여기에 경로 추가를 해주면 다른 파일에 선언된 헤더 파일도 include할수가 있다. + + + +-fPIC option을 주어서 so파일을 만든다 이때 static lib처럼 바로 실행하면 안될것이다(shared lib를 찾을수 없어서 발생하는 문제) + +해결 방법 +1. LD_LIBRARY_PATH에다가 shared library 갖고 있는 디렉토리 정보 넣어주면 됨 +https://change-words.tistory.com/entry/linux-LDLIBRARYPATH + +2. 현재 디렉토리에 shared file과 main 파일이 있고 실행 파일을 만들고 싶다면 +g++ main.o -L. -l(shared_file_name) -Wl, -rpath=. + + +### contexpr + +C++ 17부터 도입된 문법 이다. +compile 시간에 계산되는 값을 결정해준다. + + +피보 나치 함수 보면 런타임으로 만들어주면 O(n)으로 만들수있다 하지만 예제를 위해 recursive version으로 만들면 재귀 함수로 불려지고 하는 과정이 포함되지만 constexpr사용하면 컴파일 시간에 계산된 값을 직접 d에 할당 한다. 하지만 compile시간이 넘어가는건 아무래도 소용이 없는것 같다. + +```c++ +constexpr int fib(int n) +{ + if(n<=1): + { + return n; + } + else + { + return fib(n-1)+fib(n-2); + } +} + + +int main() +{ + int d = fib(10); +} + +``` + +함수나 변수에 해당 키워드 사용할수있다. +https://en.cppreference.com/w/cpp/language/constexpr + diff --git a/_posts/2023-05-07-c++_Memory.md b/_posts/2023-05-07-c++_Memory.md new file mode 100644 index 000000000000..7fdfcca3b679 --- /dev/null +++ b/_posts/2023-05-07-c++_Memory.md @@ -0,0 +1,89 @@ +--- +layout: single +title: "c++ 메모리" +categories: [c++,memorry,heap,stack,stackframe] +tag : [c++,C++] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +참고 영상 +[코드 없는 프로그래밍 c++ memory](https://www.youtube.com/playlist?list=PLDV-cCQnUlIYbHztmY7hFUCd2eGI7sQ_0) + + +### stack frame + +
+
+ +할당과 해제가 함수 단위로 이루어진다 아래 코드를 보면 이해가 될것이다 + +
+
+ + +```c++ +class Cat +{ + int m_age; +public: + //default constructor + Cat() + :m_age(1) + { + + }; + ~Cat(){}; + + void addAge(int arg) + { + m_age+=arg; + }; +}; + +int main() +{ + Cat cat; + cat.addAge(10); + return 0; +} +``` + +
+
+ +1. main함수가 먼저 쌓인다 +2. Cat class 생성자가 들어가서 m_age를 초기화 한다 그리고 this에 class 주소 정보가 들어간다 +3. addAge()함수가 stackframe에 들어간다 이때 object address 가지고 있는 this도 같이 올라간다 +그렇기 때문에 addAge()함수에서 cat class 멤버 변수를 수정 할수있는것이다 this에 object address가 있으니까 Cat class에 접근 할수있다. + + +
+
+ + +___________ + +### heap 사용 이유 + +
+
+ +1. life cycle + + stack같은 경우 stacframe 즉 함수 단위로 할당/해제가 되기 때문에 life cycle이 짧다. +2. large size + + stack은 stackoverflow 문제가 있다 즉 크기에 한계가 있다 -> heap은 사이즈가 크다 + +3. dynamic(run time) + + heap 사용하면 run time 시간에 사용가능하다 (dynamic allocation) + + +heap 사용 할때는 포인터만 잘 다루면 된다 +메모리 누수 현상을 원천적을 막는 스마트 포인터, vector사용하면 원천적으로 누수 현상 막을수있다. + + +cf : linux에는 valgrind라는 툴을 제공한다 어디에서 누수현상이 나타났는지 알수있다 + diff --git a/_posts/2023-05-11-Lvalue_Rvalue.md b/_posts/2023-05-11-Lvalue_Rvalue.md new file mode 100644 index 000000000000..9c786436376e --- /dev/null +++ b/_posts/2023-05-11-Lvalue_Rvalue.md @@ -0,0 +1,85 @@ +--- +layout: single +title: "c++ L value R value" +categories: [c++,R value,L value] +tag : [c++] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +### L value R value + +변수 처럼 다시 사용 할수있다면 -> L value +상수 처럼 한번만 사용가능하다면 -> R value + +L value는 인자를 reference &로 받고 R value는 &&로 인자를 받는다 + +**주의 점** : +void function(&& s)로 인자 받는 함수 있을때 &&S는 R value를 받겠다는 의미이자 s 자체가 R value는 아니다 왜냐하면 함수 인자로 받은 s를 function함수 내에서 다시 사용할수있기 때문이다 그래서 move같은 명령어로 R value로 바꿔 줘야지 zero copy만들수있다 + +**주의점2** : + +```c++ + +void func(const string &s) +{ + string b = move(s) + +}; + +int main() +{ + string a = "ABC"; + func(a); + +} + +``` + +const 없는 상황에서 하면 resource ownership이 b에게 넘어가고 원래 변수는 ownership을 잃어 버린다. +하지만 const를 붙히게 되면 래퍼런스로 넘기는 값이 변하면 안되니까 a변수도 그대로 ABC가지고 b변수도 그대로 ABC가지게 된다. +move를 사용했는데 어떻게 이런 결과 나올수있냐면 const를 사용했을때 move사용하면 이때는 copy를 하기 때문이다. + + + +L,R Value를 인자로 받을수있는 함수를 만들려고 한다 인자를 reference로 받으면 R,L value모두 받을수있다 R value일때는 0copy이다 (물론 함수 안에서 인자로 받은 인자를 move를 통해 처리 했다) 하지만 L value가 인자로 왔을때는 move로 처리 하기 때문에 소유권이 없어지는 문제가 발생한다 그러면 &&로 인자를 받으면 어떻게 되나 이때는 L Value를 인자로 넘기지 못하는 문제가 발생한다 그러면 L,R 모두 인자로 받으면서 L value는 1copy, R value일때는 0copy로 만들려면 어떻게 해야할까? + +A : call by value로 인자를 받는것이다. (이러면 L,R value모두 인자로 받을수있다) +L value일때는 copy by value로 인자가 copy가 되었기 때문에 그 소유권을 move를 통해 인자 변수의 소유권 넘기면 결국엔 인자에서 1copy가 된걸로 마무리가 된다 기존 L value 소유권도 박탈 되지 않으면서 지역변수에 전달 가능 하다. 그리고 R value일때를 보면 이때가 어떻게 0copy가 될수있냐면 **copy elision**이라는 최적화 기법이 작동이 되기 때문이다 copy elision은 인자로 R value가 오면 copy를 생략하게 된다 그래서 call by value인 상황에서도 0copy를 유지 할수가 있는것이다. + + + +```c++ +void func(string s) +{ + string b = move(s) + +}; + +int main() +{ + // L-value + string a = "ABC"; + func(a); + + //R value + func("ABC") + +} +``` + + + +### RVO (return value optimize) + +**copy elision**의 종류중에 하나이다. + +https://en.cppreference.com/w/cpp/language/copy_elision + +R value를 return 할때 move를 사용해서 return 할 필요가 없다 대부분의 경우 RVO가 작동이 되고 개입이 안되는 경우에도 일반적으로 0copy가 발생한다 --자세한 이유는 모르겠다. +결론은 return할때 move 사용해서 return 할 필요 없다. + + diff --git a/_posts/2023-05-15-advance_type.md b/_posts/2023-05-15-advance_type.md new file mode 100644 index 000000000000..bfee02422bdb --- /dev/null +++ b/_posts/2023-05-15-advance_type.md @@ -0,0 +1,216 @@ +--- +layout: single +title: "advance type 정리 " +categories: [c++,floating, Union,variant,pair, tuple, +type punning,optional] +tag : [c++] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + + +### 부동소수점 정리 + + +-380000=-3.8*10^4 등등 이런 부동 소수 특징은 + +IEEE 754 +32bit 기준 + + +1. 부호 1bit +2. 유효 숫자 8bit +3. 2의 몇제곱(exponantion) 23bit + + + + + +```c++ + +#include +using namespace std; + +int main() +{ + const float num1 = 0.3f; //0.30000000119 + const float num2 = 0.4f; //0.40000000596 + + const float result = num1 + num2; //0.70000001xx + + + //0.7f == 0.69999998 + if(result == 0.7f) + { + cout << "sum is correct" << endl; + } + else + { + cout << "sum is not correct" << endl; + } + return 0; +} +``` + + +sum is not correct나오는 이유 :2진법 floating 표현이 가지는 한계 때문이다 무슨말이냐면 0.3이 진짜 0.3이 아니라 최대한 근사한 값으로 +0.3을 표현 하기 때문에 발생하는 문제라는 것이다 +실제로 result에는 0.70000046이런식으로 계산이 되어있었다 + + + +**해결 방법** + +차이가 거의 없다면 똑같다고 판단하는 함수를 만들면 된다 +이때 인자로 들어오는 값에 비례해서 차이를 계산 해주어야 한다는 점이다 + +**epsilon** +차이를 계산해준다 + +```c++ +#include +#include + +using namespace std; + +//cpperference eplsilon +//ulp는 또다른 scale 값이다 +template +typename std::enable_if::is_integer, bool>::type +almost_equal(T x, T y, int ulp) +{ + // the machine epsilon has to be scaled to the magnitude of the values used + // and multiplied by the desired precision in ULPs (units in the last place) + return std::fabs(x - y) <= std::numeric_limits::epsilon() * std::fabs(x + y) * ulp + // unless the result is subnormal + || std::fabs(x - y) < std::numeric_limits::min(); +} + + +int main() +{ + const float num1 = 0.3f; //0.30000000119 + const float num2 = 0.4f; //0.40000000596 + + const float result = num1 + num2; //0.70000001xx + + + //0.7f == 0.69999998 + if(almost_equal(0.7f,result,1)) + { + cout << "sum is correct" << endl; + } + else + { + cout << "sum is not correct" << endl; + } + return 0; +} +``` + + +그래서 중요한 통찰은 float나 doble 값을 사용할때는 equal,< ,>!=... 비교하는 연산자는 사용하면 안된다!!! + + +또하나 조심해야 하는건 float,doble 두변수의 합이나 차가 클때 계산 결과값 달라진다 + + +________ + +### pair,tuple + + +tuple은 get()을 통해 접근 한다 +https://en.cppreference.com/w/cpp/utility/tuple + + + +```c++ +#include +#include +#include +#include +using namespace std; + +std::tuple get_student(int id) +{ + switch (id) + { + case 0: return { 3.8, 'A', "Lisa Simpson" }; + case 1: return { 2.9, 'C', "Milhouse Van Houten" }; + case 2: return { 1.7, 'D', "Ralph Wiggum" }; + case 3: return { 0.6, 'F', "Bart Simpson" }; + } + + throw std::invalid_argument("id"); +} + +int main() +{ + pair numstr = { 1,"meang" }; + cout << numstr.first << " " << numstr.second << endl; + + cout << "---------------------------------------------\n"; + + const auto student0 = get_student(0); + std::cout << "ID: 0, " + << "GPA: " << std::get<0>(student0) << ", " + << "grade: " << std::get<1>(student0) << ", " + << "name: " << std::get<2>(student0) << '\n'; + + const auto student1 = get_student(1); + std::cout << "ID: 1, " + << "GPA: " << std::get(student1) << ", " + << "grade: " << std::get(student1) << ", " + << "name: " << std::get(student1) << '\n'; + + double gpa2; + char grade2; + std::string name2; + std::tie(gpa2, grade2, name2) = get_student(2); + std::cout << "ID: 2, " + << "GPA: " << gpa2 << ", " + << "grade: " << grade2 << ", " + << "name: " << name2 << '\n'; + + C++17 structured binding: + const auto [gpa3, grade3, name3] = get_student(3); + std::cout << "ID: 3, " + << "GPA: " << gpa3 << ", " + << "grade: " << grade3 << ", " + << "name: " << name3 << '\n'; + + return 0; +} +``` + + +### 업데이트 해야하는 내용들 + +1. c++ optional : 값이 있을수도 있고 없을수도 있는 그런 개념이다 - 필요할때 다시 보면 될것 같다 +그러나 optional은 성능에 중요한 영향을 주진 않는다 + + + +2. Union type +> 메모리 공간을 공유 함으로써 메모리 saving하는 목적이다 + + + +3. std::variant c++17 +union type의 단점을 보완 한거다 + +variant v; + +여러가지 타입을 다룰수 있다 + + + +4. type punning + +업데이트 할 예정 + + diff --git a/_posts/2023-05-15-set_map_unordered.md b/_posts/2023-05-15-set_map_unordered.md new file mode 100644 index 000000000000..f60c509794bf --- /dev/null +++ b/_posts/2023-05-15-set_map_unordered.md @@ -0,0 +1,231 @@ +--- +layout: single +title: "set map unorderd set, unordered map 정리 " +categories: [c++,set,hash,bucket count,bucket,unorderd_set,unordered_map, map, red block tree, BST] +tag : [c++] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + + +### set + +중복 허용하지 않는다 중복 허용하려면 multiset을 사용하라. +내부적으로는 BST 또는 red block tree로 구현이 되어있다 즉 정렬이 되어있다. + +탐색 , 삽입 , 삭제 모두 O(log n )이다. +set fuction 따라서 set구성할수있다 +set과 class를 같이 쓸때는 compare operator가 있어야 한다 + +정렬 되어있는데 요소 접근 하는 operator지원하지 않는다 +**최대값은 *set.rbegin() , 최솟 값은 *set.begin()이다** + +
+
+ +* Lower bound: first element that is greater-or-equal. + +
+
+ +* Upper bound: first element that is strictly greater. + + + +==> 기준이 여러개인 경우에 잘 써먹을수있다! + +
+
+ +>그리고 lowerbound,upper bound에 greater()를 사용할수가 있다 이건 동일한 값 또는 작은 값을 출력하는건데 이때 순서가 맞아야 한다 그래서 +sort greater를 사용해서 역순으로 내림차순 정렬을 해야 한다 이건 vector등을 사용할때이고 set을 사용해야 한다고 하면 마찬가지로 set>를 사용해서 역순으로 만들고 나서 lower bound greater를 적용할수가 있다!!! + +
+
+ + + + + + + + + + + + +**multi set ,set, map모두 정렬되어있고 log n이 걸리니까 sort사용하는것보다 더 이득인가에 대한 답변** + + + +https://stackoverflow.com/questions/50198839/storing-in-stdmap-stdset-vs-sorting-a-vector-after-storing-all-data#:~:text=Using%20a%20set%2C%20you%20get%20O%20%28log%20%28N%29%29,std%3A%3Asort%20have%20O%20%28N%20log%20%28N%29%29%20on%20average.%29. +____________________ + +### map + +key와 value로 이루어져 있다 python에서 dictionary와 같다. set처럼 BST 또는 red block tree로 구성 되어있다 비교 할때는 key 값을 기준으로 비교 한다 + + +**key값 기준으로 정렬이 되어있는 결과를 얻을수있다 ==> ordered_map인것이다** + + + +주의점 : 지정하지 않는 key값에 대해서도 default 값 들어간다 조심해야함 + +탐색, 삽입 , 삭제 => O(log n) + + +____________________ + +### unordered_set + +set에 정렬 기능만 없는것이으로 hash로 이루어져 있다 + +탐색 ,삽입,삭제 가 O(1) 이다 + +hash값에 따른 bucket을 구해서 bucket들어가면 연결리스트로 연결되어있다(hash값이 크니까 여러개의 bucket을 사용한다 hash%bucket count로 버킷 인덱스를 찾는다) + +여기서 찾는 값이있는지 없는지만 파악하면 되므로 탐색 삽입 삭제가 O(1)인것이다 + +linked list에 요소가 계속 많아지면 결국 bucket count가 늘어남 -> rehashing이 일어난다 - O(n) 미리 충분한 메모리를 확보 해야 하는 이유이다 + +**max_load factor** + +bucket에 어느정도 요소가 있으면 rehashing하는지 직접 설정하는거다 비율로 계산을 한다 만약 bucket count =100 ,linked list요소들도 100이면 +비율이 1이다(default)이때 rehashing일어나는데 이 비율을 max load factor로 조절 하면 50개일때 rehashing하는등 조절 할수가 있다 자세한 사항은 cppreference참고 할것! + + +**object를 사용하려면** + +class 에서 unordered set 사용하려면 equal 와 hash를 만들어야한다 -> hash 정보가 없으면 class를 대상으로 unorderd set사용할수가 없다 +hash 함수는 custom으로 만들수도 있고 namespace안에 적용 하는 방법도 있다 + +equal이 필요한 이유는 같은 bucket안에서 서로 다른 object임을 구분하기 위해서 필요한 개념인것이다 + +unordered_set cats; -> 만든 hash 함수를 같이 넘겨야 한다 + + +**unordered_map** + + +unorderd set과 비슷하다 hash 로 구성됨 탐색 삽입 삭제 모두 O(1) 이다 map처럼 squal blacket접근 사용은 주의해야한다 +reserve를 통해 rehashing 막아야함 + + +
+
+ + +```c++ +#include +#include +#include +#include +/* + Unordered set Unordered map + + hash function 사용한다 여러개의 bucket에 나누어 담는다 + + + + +*/ + +using namespace std; + +class Cat +{ + int _age; + string _name; + +public: + Cat(string name, int age) + :_name(name), _age(age) + {} + void speak() const + { + cout << _name << " " << _age << endl; + } + const int age() const + { + return _age; + } + const string name() const + { + return _name; + } + +}; + +//hash +struct CatHash +{ + std::size_t operator()(Cat const& c) const noexcept + { + std::size_t h1 = std::hash{}(c.name()); + std::size_t h2 = std::hash{}(c.age()); + return h1 ^ (h2 << 1); // or use boost::hash_combine + } +}; + +//equal + +bool operator==(const Cat& lhs, const Cat& rhs) +{ + return(lhs.age() == rhs.age() && lhs.name() == rhs.name()); +} +int main() +{ + unordered_set unordSet; + + unordSet.emplace("abc"); + unordSet.emplace("def"); + unordSet.emplace("ghi"); + unordSet.emplace("jkl"); + + cout << "bucket count : " << unordSet.bucket_count() << endl; + + cout<<"abc : "<{}("abc")<<" bucket is : "<{}("def")<<" bucket is : "<{}("ghi")<<" bucket is : "<{}("jkl")<<" bucket is : "< cats; + cats.emplace("kitty", 1); + cats.emplace("nabi", 2); + + + + + + + cout << "-----------------------------------unordered map --------------------------------------------\n"; + + unordered_map id; + + id.emplace(1,"nocope"); + id.emplace(2,"noco"); + id.emplace(3,"cope"); + id.emplace(1, "ddddd"); + + + for (auto& ele : id) + { + cout << ele.first << " " << ele.second << endl; + } + + + return 0; +} +``` diff --git a/_posts/2023-05-15-vector_array.md b/_posts/2023-05-15-vector_array.md new file mode 100644 index 000000000000..5b8d6605fcf9 --- /dev/null +++ b/_posts/2023-05-15-vector_array.md @@ -0,0 +1,585 @@ +--- +layout: single +title: "vector 문법 정리" +categories: [c++,vector,array,range,span] +tag : [c++] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +
+
+ +이번 시간에는 vector에 대한 기본적인 내용을 정리 할것이다. 익숙해지는거에는 역시 많이 사용해봐야 할것 같다. 이 시리즈의 저작권은 introduction에 소개 되어있다. + +
+
+ +_______________________ + + +### vector + +
+
+ + +https://en.cppreference.com/w/cpp/container/vector + + +**사용이유** + +1. 동적 할당 해제 자동으로 해준다 +2. 지원하는 기능이 많다 + + +**배열의 특성을 모두 가진다** + +- random access O(1) +- 마지막요소에 삽입 또는 마지막 요소 삭제 O(1) +- 다른곳에 insertion은 O(n) 한칸씩 뒤로 밀린다 삭제도 마찬가지 + + + +벡터는 기본만 있어도 사이즈를 가진다 64bit에서는 8byte의 포인터 변수가 3개 있기 때문이다 + +1. 첫번째 요소 가리키는 포인터 +2. size포인터 +3. capacity포인터 + + +하지만 vector는 heap, array는 stack영역이다 보니까(compile 시간에 결정) array가 더 빠르다 어느정도 큰 메모리가 아니라면 array 사용하는게 좋다. + + + +
+
+ +---------------------- + +### push_back 대신 emplace_back을 사용해야 하는 이유 + +
+
+ +push_back,emplace_back 모두 인자에 L value : copy, R value : move된다. 하지만 object가 왔을때 임시로 object만들어지고 move를 하게 되는데 c++17의 emplace_back부터는 move없이 바로 object 생성을 할수가 있다. + +Cat& cat =cats.emplace_back("kitty",2) 이렇게 하는게 가장 좋다 쓸데없는 move없어진다. + + +
+
+ +---------------------- + +### 메모리를 충분히 확보 해야 하는 이유 + +벡터에서 마지막 요소 삽입 삭제 할때 O(1) 보장 한다고 했는데 가끔 O(n)이 되는 상황이 있다. -> 결론적으로 vector가 heap을 사용하기 때문이다. + + + +capacity는 벡터가 확보한 메모리 크기이다 그래서 reserve를 통해 메모리 용량을 조절할수있다 이렇게 하는 이유는 heap은 듬성듬성이라서 메모리를 추가 할때 추가하는 공간이 이미 쓰일수도 있다. 그래서 vector는 이런 경우 copy하거나 move를 한다 ->비효율 그래서 미리 충분한 용량을 확보하는게 좋다. +
+
+ +또한 rule of five,three에 따라 직접 move constructor, copy constructor을 구현한 클래스와 vector를 사용할때 O(1)의 성능이 O(n)으로 나올수가 있다. 앞에 문제처럼 heap공간을 미리 쓰고 있는 공간일때 noexcept를 move constrouctor에 사용하지 않으면 move도 일어나지 않고 copy가 일어나게 된다 그래서 move constructor 앞에 noexcept 키워드를 사용하자. 물론 reserve를 통해 미리 충분한 공간을 확보하면 상황은 발생하지 않는다. + +---------------------- + +### vector 반복문 표현 대표적인 3가지 + + +**반복문 유형** + +> 1. index base + 2. iterator base + + > 백터의 itreators 반복자 + + v.begin() 벡터 시작점의 주소값 반환 + v.end() 벡터 끝부분+1의 주소값 반환 (주의점 : 배열 벗어남) + v.rbegin() 벡터의 끝 지점을 시작점으로 반환 + v.rend() 벡터의 시작+1 지점을 끝 부분으로 반환 + + 정리 : 벡터 시작 부분 주소값 begin 벡터 끝부분 rbegin + 벡터 시작+1 부분 rend() 벡터 끝 부분+1 end() + + + 3. range base + +
+
+ + +벡터 사이즈가 변경 되는 경우라면 인덱스 기반의 for loop 사용해라 - 앞에 말한 상황(heap을 누가 미리 쓰고 있는 경우)이 발생하면 iterator는 잘못된 주소를 가리키게 되어서 문제가 된다 range의 경우도 비슷한 문제 때문에 문제가 발생하게 된다 하지만 이 역시도 충분한 공간이 마련되어있다면 문제 없이 작동한다. + +
+
+ + +```c++ + vector numA(10, 1); + + //index base + for (int i = 0; i < numA.size(); i++) + { + numA[i] *= 2; + } + + + //iterator base + + //std::vector::iterator itor = c.begin(); + for (auto iter = numA.begin(); iter != numA.end(); iter++) + { + (*iter) *= 2; + } + + //range base 자주 사용하는 형태이다 + for (auto &n:numA) + { + n *= 2; + } + +``` + + +---------------------- + +### remove,erase + +**erase** + +https://en.cppreference.com/w/cpp/container/vector/erase + + +**remove, remove_if** + +https://en.cppreference.com/w/cpp/algorithm/remove + +
+
+ +여러개의 요소를 지워야 할때 erase를 사용하면 O(n)작업이 여러번 발생하게 된다. 하지만 erase와 remove를 같이 사용하면 O(n)작업이 여러번 발생하는 문제 해결 할수가 있다. + + +**remove(begin,end,tartget)** + +remove의 작동 원리는 two pointer와 비슷하다 + + +예시 배열 [0 0 1 1 0]이 있다고 가정 하자 + +투 포인터가 있다 해당 target이 0이면 두번째 포인터는 계속 넘어가고 첫번째 포인터는 그대로 있다 두번째 포인터가 1을 만나면 첫번째 포인터 위치에 1을 move [int의 move는 copy]한다 그러면 +[1 0 1 1 0]이렇게 된다 그리고 첫번째와 두번째 포인터 다음으로 넘어간다 현재 첫 번째 포인터는 0을 가리키고 있고 두번째 포인터는 마지막 1 위치에 있다 두번째 포인터가 또 1이니까 첫번째 포인터 위치에 copy한다 +[1 1 1 1 0] 이렇게 된다 그리고 첫번째와 두번째 포인터 다음으로 넘어간다 현재 첫번째 포인터는 3번째 1 가리키고 있고 두번째는 마지막 0을 가리킨다 두번째 포인터는 0을 지나 마지막으로 오면 첫번째 포인터 위치를 return 한다 +첫 번째 포인터 위치가 반환 (end()도 마지막 보다 한칸 더 라는거 보면 )이 위치를 erase와 같이 사용하면 +erase(arr.begin(),remove return) 결국에는 [1 1]만 남게 됨 -> 투포인터와 비슷한 원리로 remove가 이루어지고 있다 + +
+
+ +**remove_if()를 사용하면 target자리에 함수가 올수있다 (람다 사용가능 )** + + +```c++ +class Cat +{ + int _age; + string _name; +public: + Cat(string name, int age) + :_name(move(name)),_age(move(age)) + { + + } + void speak() const + { + cout << _name << " " << _age << endl; + } + int Age() const + { + return _age; + } + +}; +bool even_age(Cat& cats) +{ + if (cats.Age() % 2 == 0) + { + return true; + } + return false; +} + +int main() +{ + vector remove_arr = { 0,0,1,1,0 }; + auto iter = remove(remove_arr.begin(), remove_arr.end(), 0); + remove_arr.erase(iter,remove_arr.end()); + cout << "this is remove working" << endl; + for (auto& e : remove_arr) + { + + cout << e << endl; + } + cout << "-------------------------------------" << endl; + + vector rem_arr = { 0,1,1,0,1,0,1,0,2,4,8 }; + + //remove + rem_arr.erase(remove(rem_arr.begin(), rem_arr.end(), 1), rem_arr.end()); + + cout << "this is remove example " << endl; + for (auto& ele : rem_arr) + { + cout << ele << " "; + } + cout << endl; + cout << "--------------------------------------------\n"; + + rem_arr.erase(remove_if(rem_arr.begin(), rem_arr.end(), [](int n) { + if (n % 2 != 0) + { + return n; + } + }),rem_arr.end()); + + for (auto& ele : rem_arr) + { + cout << ele << " "; + } + cout << endl; + cout << "--------------------------------------------\n"; + + + //remove if object class case + + + vector cat_arr; + cat_arr.reserve(5); + + cat_arr.emplace_back("cat1", 2); + cat_arr.emplace_back("cat2", 5); + cat_arr.emplace_back("cat3", 6); + cat_arr.emplace_back("cat4", 4); + cat_arr.emplace_back("cat5", 7); + + cat_arr.erase(remove_if(cat_arr.begin(), cat_arr.end(),even_age),cat_arr.end()); // 꼭 lambda 표현 안써도 상관 없다 + + + cout << "this is cat remove if!" << endl; + for (auto& e : cat_arr) + { + e.speak(); + } + cout << "------------------------------------" << endl; +} + +``` + + +---------------------- + + +### 관련 알고리즘 + +**sort** +sort가 지원하는 기본 성능은 O(n logn)이다. + > stable sort 기준되로 정렬하돼 그외에 것은 순서대로 한다 같은값애 대해서 순서를 바꾸지 않는다 stable sort를 따로 지원한다 + + 이외에도 + + >partial sort (부분 정렬)도 지원한다 말그대로 부분만 정렬을 한다 이런게 나온 이유는 sort의 time complexity가 O(n logn)이라서 조금이라도 줄이기 위함이다. std::partial_sort(begin,begin+3,end) + + + > nth_element( RandomIt first, RandomIt nth, RandomIt last ) + >https://en.cppreference.com/w/cpp/algorithm/nth_element + + + >기본적으로 n번째의 요소가 정렬이 되었을때의 값을 알수있다 return 값은 없으니 직접 출력해야한다 중요한건 vector가 다시 재정렬된다 nth기준으로 왼쪽은 작은값들 오른쪽은 큰값들이 온다 ->정렬은 안됨 + + + + +```c++ +int main() +{ + + vector soArr = {8,6,3,4,2,1,9,1,41}; + + //sort -> nlogn 함수도 사용가능 + + //partialsort 부분 정렬 + + partial_sort(soArr.begin(), soArr.begin() + 3, soArr.end()); + + for (auto& e : soArr) + { + cout << e << " "; + } + cout << endl; + + cout << "--------------------------------------------\n"; + + //nth_element + + nth_element(soArr.begin(), soArr.begin() + soArr.size() / 2, soArr.end()); + + cout << soArr[soArr.size() / 2]< +
+ +**find** + +복잡도는 O(n)찾은 요소의 첫 주소를 반환 한다 end()를 return 하면 못찾은것이다. index를 알고 싶다면 distance를 같이 활용하면 알수있다 만약 정렬이 되어있는 상황에서 find를 한다고 하면 O(n)말고 O(log n)으로 해결 할수가 있는데 binaray_search를 사용하면 되기 때문이다. + + + + +```c++ + //find + + auto result = find(rem_arr.begin(), rem_arr.end(), 2); + if (result != rem_arr.end()) + { + cout << "founded" << endl; + cout << "idx : " << distance(rem_arr.begin(), result)< +
+ +**max_element,min_element,minmax_element** + +사실 c++ 20부터 range를 지원하는데 range에서도 같은 기능을 제공한다 뿐만아니라 find,count등등 다양하게 지원하는게 많다 이건 따로 포스팅해서 정리 할 예정이다. + +
+
+ +>Defined in header +>min_element +>요소중에 최소 값을 반환 + +>max_element +>요소중 최대 값을 반환 +>주소를 반환하니까 +>iterator랑 같이 쓰인다 + +>minmax_element +>최소 최대 동시에 알수있다 +>쌍으로 반환됨 + + +```c++ + vector::iterator it; + vector::iterator itt = soArr.begin(); + + it = min_element(itt, soArr.end()); + cout << "min element is : " << *it <<" idx is " << distance(itt, it) << endl; + + it = max_element(itt, soArr.end()); + cout << "max element is : " << *it << " idx is " << distance(itt, it) << endl; + + + //minmax_element 사용방법 + //주소를 반환하니까 iterator를 써서 주소값을 받고 그다음에 출력할때는 point이용하면 된다 + + pair::iterator, vector::iterator> a1 = minmax_element(itt, soArr.end()); + + cout << "min value from minmax_element is : " << *a1.first << endl; + + cout << "max value from minmax_element is : " << *a1.second << endl; + + cout << "--------------------------------------------\n"; + + +``` + +
+
+ +**reduce,accumulate** + +>reduce +>accumulate보다 더 빠르게 처리가능 병렬처리 할때 사용됨 + + +>accumulate Defined in header + +>T accumulate( InputIt first, InputIt last, T init ); T init 에는 초기 값이 들어간다 합을 구할때 초기값을 0으로 설정하는것도 그 이유이이다 + +>자주하는 실수 , T init은 int형이다 그런데 doubel s = accumulate~~ 한다고 해서 double자료형이 되는게 아니다 acuumulate의 반환값이 init 타입을 따라가기 때문이다 그러니까 초기값 설정할때 잘해야함 만약 다른 자료형으로 캐스팅을 하고 싶다면 초기값을 설정할때 캐스팅을 해야한다 (not 변수) + +```c++ + //accumulate header + + int sum = accumulate(itt, soArr.end(), 0); + int multi = accumulate(itt, soArr.end(), 1,multiplies()); + + cout << "SoArr sum is " << sum << endl; + cout << "SoArr product is " << multi << endl; + + cout << "--------------------------------------------\n"; + +``` + +
+
+ +---------------------- +### 2차원 배열 + +
+
+ +이 부분은 알고리즘을 직접 해보면서 감을 다시 찾아야 한다. + +여기서는 간단한 행렬만 보여주고 마무리 하겠다 + + +2차원을 1d로 표현하는게 더 효율적인데 이걸 구현 할수있다 -- 코드 참고 +중요한건 for loop사용할때 cahce line 방향으로 해야 한다 -> row col 순으로 이중 for문 사용해야 chach line방향과 맞다. + +
+
+ + +```c++ +//Matrix class +template + +class Matrix +{ + vector mMatrix; + int mRow; + int mCol; +public: + Matrix(int row,int col) + :mRow(row),mCol(col) + {} + + //m*n matrix mat(10,10) + T& operator()(int row_idx, int col_idx) + { + const int idx = row_idx * mCol + col_idx; + return mMatrix[idx]; + } + +}; +``` + +
+
+ + + +---------------------- +### span + +
+
+ +c++ 20부터 추가가 된 기능이다 이걸 사용하려면 앞서 말한 range와 view에 대한 지식이 필요한데 이 내용은 따로 포스팅해서 정리 하겠다. + + + +>string_view char pointer string, char array string을 string으로 인자 받으면 성능 떨어진다 이때 사용하는게 string view이다 + + + +https://en.cppreference.com/w/cpp/container/span + + +span은 연속된 공간의 시작 주소와 사이즈만 전달 한다 +이러면 vector뿐아니라 array등이 와도 처리를 할수가 있게 된다 + +**주의점** +span설정 한뒤 원본을 바꾸면 다른곳을 가리킨다 + + +span은 2가지 모드가 있다 dynamic과 static이 있는데 dynamic은 배열의 사이즈를 매번 확인하지만 static 버전을 사용해서 고정으로 바꾸면 최적화 할수있는것이다 --> 배열의 사이즈를 정해주면 static아니면 dynamic인것 같다 + + +```c++ +//c++ 20 span + +void print_span(span nums) + +{ + for (auto& e : nums) + { + cout << e << endl; + } + + auto ret = ranges::find(nums, 3); + + if (ret != nums.end()) + { + cout << "found!!" << endl; + } + + auto count = ranges::count(nums.begin(), nums.end(), 3); + + if (count != 0) + { + cout << "counted " << count << endl; + } + else + { + cout << "zero counted" << endl; + } + + +} + +int main() +{ + //c++ 20 span 여러가지 배열에 대해서 대응을 할수가 있다 + + vector v_n{ 1,2,3 }; + array a_n{ 3,3,3,3,3,4,5,6 }; + + //c style array + int c_n[9] = { 1,2,3,4,5,6,3,3,7 }; + + //dynamic version + const std::span sp{ v_n }; //이후에 배열을 바꾸면 안된다 -- 주의점 + const std::span fixed_sp{ a_n }; + + print_span(sp); + print_span(fixed_sp); +} +``` + + +
+
+ +---------------------- diff --git a/_posts/2023-05-25-ranges_c++20.md b/_posts/2023-05-25-ranges_c++20.md new file mode 100644 index 000000000000..bbc5e6522469 --- /dev/null +++ b/_posts/2023-05-25-ranges_c++20.md @@ -0,0 +1,383 @@ +--- +layout: single +title: "Ranges libary in std:c++20" +categories: ranges +tag: [c++,c++20,ranges,Ranges] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +### Ranges libary + +오늘은 c++20부터 추가된 ranges에 대해 정리 할것이다. + +[cppcon ranges youtube vidieo](https://www.youtube.com/watch?v=d_E-VLyUnzc&t=56s) +{: .notice--danger} + +
+
+ +[cppreference ranges](https://en.cppreference.com/w/cpp/ranges) + + +[cppreference ranges algorithms](https://en.cppreference.com/w/cpp/algorithm/ranges) + +[참고사이트1](https://junstar92.tistory.com/305) + +[참고사이트2](https://jungwoong.tistory.com/56) +[참고사이트3](https://sorting.tistory.com/22) +[참고사이트4](https://openmynotepad.tistory.com/88) +[참고사이트5](https://openmynotepad.tistory.com/87) + + + +
+
+ + +### 사용 이유 + + +1. iterator 관련 알고리즘 사요할때 유용하게 사용할수있다 -> iterator의 확장 +2. 파이프 구문 (|) 사용가능 +3. 필요할때만 계산할수있다 (lazy operation concept) + + +**string 파트에 string_views 정리 한거 있는데 참고 하면 아래 내용 이해하는데 도움이 된다!** + +
+
+ +### views + +views와 borrowed ranges는 viewable range이다 + + +**transform** +std::transform은 for_each와 동일하다 하지만 transform은 원소를 수정하게 된다 다음 코드를 보고 transform이해 할수 있다 + + +```c++ +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +//13번째 문자부터 대응하는 함수 +//a이면 n이고 b이면 o이다 아스키 코드 보면 이해 가능 +char rot13a(const char x, const char a) +{ + return a + (x - a + 13) % 26; +} + +char rot13(const char x) +{ + if ('Z' >= x and x >= 'A') + return rot13a(x, 'A'); + + if ('z' >= x and x >= 'a') + return rot13a(x, 'a'); + + return x; +} + +int main() +{ + //char 배열 만드는 함수 + auto show = [](const unsigned char x) { std::putchar(x); }; + + std::string in{ "cppreference.com\n" }; + std::ranges::for_each(in, show); //문자열 출력 + + std::ranges::for_each(in | std::views::transform(rot13), show); //rot13함수를 적용한 in 문자열을 출력하기 + + + std::string out; + std::ranges::copy(std::views::transform(in, rot13), std::back_inserter(out)); + std::ranges::for_each(out, show); + + //다시 원상 복구 한다 + std::ranges::for_each(out | std::views::transform(rot13), show); + + + + + vector b; //b에 a-z까지 알파벳 + + for(int i=97;i<123;i++) + { + b.emplace_back(static_cast(i)); + } + + + //주의!! view로 정의 함 + auto view {views::transform(b,rot13)}; + + + //위와 같은 결과 나온다 + for(auto iter=view.begin();iter v = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + auto r = std::ranges::views::all(v); +``` +vector를 range r로 만든다 하나의 값만 range로 바꾸려면 single()사용하면 된다 + + + +**drop,drop_while** + + +문자열 다듬는 작업 -> triming이라고 한다 +"\n\t\r Hello World! \n\n" 를 "Hello World!" 로 변경해 보는 예제를 통해 알아보자. + + +우선 drop(n)은 처음 n 요소를 스킵하는 것이다 + +drop_while(condition)은 condition이 만족하면 계속 drop하는 것이다 + + +위의 문자를 hello world 문자로 바꾸려면 공백을 앞에서 뒤에서 없에야 한다 그래서 trimFront가 hello앞에 있는 공백을 제거 하고 trimback이 world 뒤에 있는 공백을 제거 한다 + + +```c++ +auto trimFront() +{ + return std::views::drop_while(::isspace); +} + +auto trimBack() +{ + return std::views::reverse | std::views::drop_while(::isspace) | std::views::reverse; +} + +//triming function + +string String_trim(const std::string_view& str) +{ + auto trimString{ + str|trimFront()|trimBack() + }; + + //string type으로 반환 한다 + return std::string{trimString.begin(),trimString.end()}; + + +} +``` + + +**views::filter** + +```c++ +int main() { + auto even = [](int elem) { return elem % 2 == 0; }; + + for (int i : std::views::iota(1, 100) | + std::views::filter(even)) { + std::cout << i << std::endl; + } +} + +``` +함수 보면 감이 올것이다 조건에 맞는것만 filtering한다 -> python에서도 비슷한 기능을 하는 filter함수가 있다 +참고로 iota는 초기값 부터 1식 증가 해주는 함수이다 + + + +**iota_view** + +시작점과 끝점을 기준으로 일정하게 증가하는 수열을 나타내는 view이다 +범위를 지정 하지 않으면 무한히 증가 하는 수열이 된다 + +포인터의 범위로 사용 될수도 있다 + +```c++ +int main() { + + int array[] = {2, 4, 6, 8, 10}; + + // 포인터 시퀀스 + std::ranges::iota_view iva{std::ranges::begin(array), std::ranges::end(array)}; + + for (int* p : iva) { + std::cout << *p; // 246810 + } + + std::cout << '\n'; + + std::list list = {1, 3, 5, 7, 9}; + + // list 컨테이너의 iterator를 사용한 시퀀스 + std::ranges::iota_view ivl{std::ranges::begin(list), std::ranges::end(list)}; + + for (auto it : ivl) { + std::cout << *it; // 13579 + } +} + +``` + + + +**istream_view** + +입력 받을때 유용하게 사용 할수 있을것 같다. type에 조심 해야 한다 +-> 당연히 vector와 같이 연계해서 사용할수가 있다 + + + +```c++ +#include +#include +int main() +{ + for (int n : std::ranges::istream_view(std::cin)) + { + std::cout <<"output : "< vec = get_input(); + +auto iter = std::min_element(vec.begin(),vec.end()); + +``` + +**range사용 방법** + +```c++ + +//c++20 version +std::vector vec = get_input(); + +auto iter = ranges::min_element(vec.begin(),vec.end()); + +``` + +vector vesrion stl 알고리즘을 사용하면 begin과 end를 사용해야 하지만 range를 사용하면 vec(L -value)만 인자로 넘겨도 된다 + +하지만 r -value의 object life time error 때문에 문제가 될수있지만 ranges에 r-value를 인자로 넘기면 range는 rages::dangling object type(empty class type)으로 반환을 해준다 즉 ranges는 r value에 대해서 "dangling protection"을 제공한다 + + +dangleing object는 *iter값 사용하면 compile error가 나온다 이때 enable_borrowed_range = true를 사용해볼수있다 dangling object로 return하지 않아서 *iter값 사용할수가 있다 자세한건 cppreference참고! + +cf : span,string_view같은건 enable_borrowed_range하지 않아도 이미 사용가능하다. + + +
+
+ +**range adaptor는 viewable range or borrowed ranges (l value,enable_borrowed_ranges 특성 가지는것)에서만 작동한다** + +
+
+ + + +### ranges algorithms + + +**ranges::search** + + +string 문제에서 rolling hash를 직접 구현 하였다 다음 코드는 search를 사용해서 rolling hash문제를 해결한 코드이다 + + +```c++ +#include +#include +#include +#include +#include + +using namespace std::literals; + +void print(int id, const auto& haystack, const auto& needle, const auto& found) +{ + std::cout << id << ") search(\"" << haystack << "\", \"" << needle << "\"); "; + const auto first = std::distance(haystack.begin(), found.begin()); + const auto last = std::distance(haystack.begin(), found.end()); + if (found.empty()) + std::cout << "not found;"; + else + { + std::cout << "found: \""; + for (const auto x : found) + std::cout << x; + std::cout << "\";"; + } + std::cout << " subrange: {" << first << ", " << last << "}\n"; +} + +int main() +{ + constexpr auto haystack {"abcd abcd"sv}; + constexpr auto needle {"bcd"sv}; + + // the search uses iterator pairs begin()/end(): + constexpr auto found1 = std::ranges::search( + haystack.begin(), haystack.end(), + needle.begin(), needle.end()); + print(1, haystack, needle, found1); + + // the search uses ranges r1, r2: + constexpr auto found2 = std::ranges::search(haystack, needle); + print(2, haystack, needle, found2); + + // 'needle' range is empty: + constexpr auto none {""sv}; + constexpr auto found3 = std::ranges::search(haystack, none); + print(3, haystack, none, found3); + + // 'needle' will not be found: + constexpr auto awl {"efg"sv}; + constexpr auto found4 = std::ranges::search(haystack, awl); + print(4, haystack, awl, found4); + + // the search uses custom comparator and projections: + constexpr auto bodkin {"234"sv}; + auto found5 = std::ranges::search(haystack, bodkin, + [](const int x, const int y) { return x == y; }, // pred + [](const int x) { return std::toupper(x); }, // proj1 + [](const int y) { return y + 'A' - '1'; }); // proj2 + print(5, haystack, bodkin, found5); +} + + +``` + + +**ranges::partition** + +어떤 조건(함수포함)에 의해 두개의 그룹으로 나눈다 + +시간 복잡도는 N이고 return value는 두번째 그룹에서 첫 요소의 주소를 반환한다 그래서 auto tail = ranges::partition(v, [](int i) { return i % 2 == 0; }); v 벡터가 0~9까지의 요소를 가진 벡터라고 하면 조건에 따라 홀수와 짝수 그룹으로 나누어질것이다 함수 조건이 T인 경우가 첫번째 그룹이다 그래서 홀수가 두번째 그룹이 되는데 순서는 보장 하지 않는다 두번째 그룹의 첫 요소를 가리키는 포인터가 tail.begin이다 순서를 보장 하고자 하면 ranges::stabel_partition을 사용할수있다 하지만 시간 복잡도가 최대 N logN이 걸린다는 단점이 있다 \ No newline at end of file diff --git a/_posts/2023-07-03-sort_problem.md b/_posts/2023-07-03-sort_problem.md new file mode 100644 index 000000000000..3ee251a56caa --- /dev/null +++ b/_posts/2023-07-03-sort_problem.md @@ -0,0 +1,342 @@ +--- +layout: single +title: "정렬 문제모음" +categories: Sort,Sort proplem +tag : [c++,python,Algorithm,LeetCode,Sort,problem] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +### Intro + +sorting 개념은 따로 정리 했으니 참고 하면 도움이 될것이다. 이 포스팅에서는 정렬관련 문제들을 모아 보았다 + + + +https://leetcode.com/problems/sort-array-by-increasing-frequency/ + +-> 우선 순위 큐 내용 힙 내용 배우고 나서 힙 정렬 문제 할때 풀어보기 +-> counting sort 변형해서 풀어보려고 하였다 그러나 음수의 경우도 고려를 해야 하였기 때문에 누적 합을 이용한 자릿수는 안될것 같았다 그렇다고 std::count를 사용해서 매번 O(n)만큼 탐색하고 풀어내는것도 조금 그랬다 그래서 답안을 보았는데 우선 순위 큐를 이용해서 문제를 푸는것 같더라.. 거의 2시간 가까이 보고있었는데 결국 하지 못함 미래의 나 화이팅 + + +
+
+ +### 문제 모음 + + +[array partition](https://leetcode.com/problems/array-partition/description/) +{: .notice--danger} + + + +[sort_color_leetcode](https://leetcode.com/explore/learn/card/sorting/694/comparison-based-sorts/4483/) +{: .notice--danger} + +[수 찾기](https://www.acmicpc.net/problem/1920) +{: .notice--danger} + +[Longest Subsequence With Limited Sum](https://leetcode.com/problems/longest-subsequence-with-limited-sum/description/) + + +[intersection of two arrays I](https://leetcode.com/problems/intersection-of-two-arrays/description/) + + + + +[intersection of two arrays II](https://leetcode.com/problems/intersection-of-two-arrays-ii/) + + +------------------------------- + +### array partition - leetcode + +정렬 문제인지를 모르고 그냥 이 문제를 보면 접근을 어떤식으로 해야할지 감이 안올것이다 실제로 감이 안왔다 정렬 문제라는 tag를 보고 나서 쉽게 접근 할수 있었다 + +```c++ +class Solution { +public: + int arrayPairSum(vector& nums) + { + int result=0; + sort(nums.begin(),nums.end()); + for(int i=0;i& nums) + { + vector count(3,0); //0,1,2 + vector result(nums.size(),0); + int acc = 0; // 누적합 변수 + + for(auto&e:nums) + { + count[e]++; + + } + for(int i=0;i-1;i--) //for stable + { + result[count[nums[i]]] = nums[i]; //count 배열에서 인덱스 정보에 값 넣는다 + count[nums[i]]--; + } + nums=std::move(result); + } +}; + +``` + +마지막에서 복사를 막기 위해 move를 통해 처리 하였다 + + +### 수찾기 백준 + + +문제는 링크 타고 가서 보면 될것 같다 처음 문제 해결 방법 떠올린것은 정렬을 하고 binary serarch로 수 찾으면 될것 같았다 시도 안해봤지만 아마 될것이다 하지만 정렬 하는데 nlogn 그리고 수찾는데(binary serarch) logn 걸리니까 O(nlogn +logn)이 소요될것으로 예상 되었다 그래서 다른 방법을 찾기로 했다 unordered set을 이용해서 O(n)으로 해결 하고 싶었다(예상 time complexty가 O(n)이지 실제로는 모르겠다 대략 계산한거라 틀릴수있다 그래서 틀리면 알려주세요) N,M 범위가 100000까지니까 reserve를 통해 미리 할당을 해주었다 그리고 o(1)으로 find하면 된다 + +```c++ +#include +#include +using namespace std; + +int main() +{ + ios::sync_with_stdio(false); + cin.tie(NULL); + + unordered_set set; + set.reserve(100000); + + long long int n=0,m=0; + cin>>n; + for(int i=0;i>temp; + set.emplace(temp); + } + + cin>>m; + for(int i=0;i>temp; + if(set.find(temp)!=set.end()) + { + cout<<1<<'\n'; + } + else + { + cout<<0<<'\n'; + } + } + + + + return 0; +} +``` + +### Longest Subsequence With Limited Sum + + +처음에 내가 풀었던 방식은 구간 합을 보고 나서 queries요소보다 커지면 인덱스를 result에 저장하는 방식이었다 + +처음에 내가 풀었던 방법 코드 + + +```c++ +class Solution { +public: + vector answerQueries(vector& nums, vector& queries) + { + sort(nums.begin(),nums.end()); + //sort(queries.begin(),queries.end()); + vector result; + + + + int i=0,j=0; + for(j=0;jq) + { + + break; + } + + + } + + //i=i-1 + result.emplace_back(i); + + } + return result; + + } +}; +``` + +문제 없이 통과 했지만 솔루션을 통해 다른 사람의 코드를 본 결과 위에서 내가 했던 방식과 똑같은 생각이지만 코드 구현에서 차이가 있어서 남긴다 + +처음 코드에서 아쉬웠던것들 + +1. prefix sum을 하는거에서 이전까지의 합만 필요했던것이라서 코드 구현에서 아쉬운거 있었다 + +2. 인덱스까지의 위치를 upper bound를 통해 해결할수있음을 알았다 + * https://en.cppreference.com/w/cpp/algorithm/upper_bound + + +```c++ + +class Solution { +public: + vector answerQueries(vector& nums, vector& que) { + vector ans; + sort(nums.begin(),nums.end()); + for(int i=1; i +
+ + +```c++ +class Solution { +public: + vector intersection(vector& nums1, vector& nums2) + { + set set; + + for(auto&e:nums2) + { + set.emplace(e); + } + nums2={}; + + for(auto&e:nums1) + { + if(set.find(e)!=set.end()) + { + nums2.emplace_back(e); + set.erase(e); + } + } + return nums2; + + + } +}; +``` +
+
+ +---------------------------------------------------------------------------------------------------------- + +
+
+ +### intersection of two arrays II + +위에서 풀었던 문제의 2번째 버전 문제이다 위의 1번 문제와 다른 점은 이제 중복 한것도 표시를 해야 한다는 것 빼곤 1번문제와 같은 문제이다 + +하지만 위 문제 처럼 중복을 허용하지 않기 때문에 더이상 set으로 문제 풀기 어렵다 왜냐 하면 num1의 요소가 몇개 있는지 확인을 해야 하기 때문이다 그래서 각 요소가 몇번 반복되는지 map을 통해 구해주고 문제를 해결한다 처음에는 위에 솔루션에서 해결 해보려고 하였는데 중복을 제거하는 set으로는 구해지지 않을것을 알게 되었고 솔루션을 통해 문제를 해결 하였다 + +
+
+ + +```c++ +class Solution { +public: + vector intersect(vector& nums1, vector& nums2) + { + unordered_map map; + vector ans; + + for(int i=0;i0) + { + map[nums2[i]]--; + ans.emplace_back(nums2[i]); + } + } + + return ans; + + } +}; + +``` \ No newline at end of file diff --git a/_posts/2023-07-07-stack.md b/_posts/2023-07-07-stack.md new file mode 100644 index 000000000000..02c80f8a8f7a --- /dev/null +++ b/_posts/2023-07-07-stack.md @@ -0,0 +1,612 @@ +--- +layout: single +title: "stack 개념 및 문제" +categories: stack,Stack proplem +tag : [c++,python,Algorithm,LeetCode,stack,problem] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + + +### stack 개념 + +First in Last out (FILO) + +연결 리스트 , 배열로 스택 표현이 가능하고 c++에서 따로 stack자료형을 지원 한다 +지원 기능 : push,top,pop,isEmpty + +
+
+ +**특징 : 임시 데이터를 다뤄야 할때 stack 자료구조는 유용한 도구이다** + +
+
+ + +1. 마지막에 들어온 요소를 처리 할수있다 + > top에서 부터 데이터 읽을수있다 즉 중간 요소부터 처리 하는 경우에 자료구조 특성상 부합한다 + +2. 마지막 요소부터 역방향으로 처리 할수있다 + +
+
+ +추가 예_ 미로 탐색이나 플러드 필 문제에서 스택은 유용할수있다 +[flood fill algorithm](https://ko.wikipedia.org/wiki/%ED%94%8C%EB%9F%AC%EB%93%9C_%ED%95%84) + +
+
+ +아래 두 문제가 스택의 개념과 이해를 잘 할수있는 문제이다 + +
+
+ + +### stack 문제 리스트 + +[valid parentheses](https://leetcode.com/problems/valid-parentheses/description/) + + +[Minimum Remove to Make Valid Parentheses](https://leetcode.com/problems/minimum-remove-to-make-valid-parentheses/description/) + + +[Min stack](https://leetcode.com/problems/min-stack/description/) + +[1047. Remove All Adjacent Duplicates In String](https://leetcode.com/problems/remove-all-adjacent-duplicates-in-string/description/) + + +[Remove All Adjacent Duplicates In String II](https://leetcode.com/problems/remove-all-adjacent-duplicates-in-string-ii/description/) + +[Decode string](https://leetcode.com/problems/decode-string/description/) + + + +### valid parentheses + +스택의 기초가 되는 문제이다 괄호가 valid한지 아닌지 판단하는 문제이다 + +해결 한 방법 : map을 사용해서 대응하는 괄호를 저장 하였고 stack이 비어있지 않고 top에 요소가 map에 저장되어있는 대응하는 괄호와 맞는지 확인한다 그래서 맞으면 pop을 한다 push되는 경우는 unvalid한 괄호가 추가 될때(판단 이전 기준)이다 그래서 마지막에는 stack이 비어있는지 아닌지에 따라 valid한지 아닌지 판단할수가 있다 + +```c++ + +class Solution { +public: + bool isValid(string s) + { + unordered_map valid_map; + stack stack_char; + + valid_map.emplace(')','('); + valid_map.emplace('}','{'); + valid_map.emplace(']','['); + + for (auto& ele : s) + { + if (!stack_char.empty() && stack_char.top() == valid_map[ele]) + { + + stack_char.pop(); + continue; + } + stack_char.push(ele); + } + + return stack_char.empty() ? 1 : 0; + + } +}; + +``` + +
+
+ + +### Minimum Remove to Make Valid Parentheses + + + +기존에는 erase를 여러번 사용해서 mO(n)이었는데 result 변수에 넣는 방식으로 바꾸었다 이 문제는 valid하면 stack은 비어있으니 그대로 return하면 되고 unvalid한 경우 unvalid한 괄호를 제거해서 vaild하게 만드는 문제인것이다 그래서 unvalid한 괄호의 위치를 알아야 하고 그 자리를 공백으로 대체 하였다 그리고 나서 공백을 없엔후 result에 할당하고 return하는 방식이다 제출해본 결과 처음 보다 2배 이상 좋았다 하지만 공간 복잡도 O(n)이 되었고 더 좋은 방식이 있을것 같다 + +1. erase 사용해서 푼 결과 + +```c++ +class Solution { +public: + string minRemoveToMakeValid(string s) + { + unordered_map valid_map; + stack> stack_char; + + valid_map.emplace(')','('); + valid_map.emplace('}','{'); + valid_map.emplace(']','['); + + for (int i=0;i +
+ +2. result 변수에 넣는 방식 +```c++ + +class Solution { +public: + string minRemoveToMakeValid(string s) + { + unordered_map valid_map; + valid_map.emplace(')','('); + valid_map.emplace('}','{'); + valid_map.emplace(']','['); + + vector> vec; + //vec.reserve(1000000); + string result=""; + + for(int i=0;i>temp) + { + result+=temp; + } + } + return result; + + + } +}; + +``` + + +3. 솔루션 찾아 보고 나서 바꾼 코드 + + + +[위에 문제 솔루션 찾아본 코드 링크](https://leetcode.com/problems/minimum-remove-to-make-valid-parentheses/solutions/3509872/100-c-solution-without-using-stack-time-complexity-o-n-space-complexity-o-1/) + + +```c++ +class Solution { +public: + string minRemoveToMakeValid(string s) { + int brackets=0,n=s.length(); + string res; + for(int i=0;i=0;i--){ + if(s[i]==')')brackets++; + else if(s[i]=='('){ + if(brackets==0) s[i]='$'; + else brackets--; + } + } + for(int i=0;i34->19ms의 결과가 나왔다 + + +2번째와 3번째 해결 차이를 보면 크게 다르지 않다 2번에서 생각했던 방식을 사용해서 3번이 풀고 있다 다만 3번이 생각을 최적화 맞게 구현을 잘했다고 볼수있다 + +회고 : +2번에서 내가 한 방식과 3번을 비교해보면 + + +1. 2번 보면 ()만 사용하는데도 {} []를 추가 하고 있다 이전 문제를 그대로 가지고 온것이 문제인데 문제 파악을 제대로 하지 못했음을 알수있다 +2. 2번은 stack 대신 vector를 사용해서 문자와 인덱스 정보를 pair로 받고 unvalid한 괄호 자리에 공백을 넣는 작업을 하고 있다 반면 3번은 vector사용하는대신 기존에 있는 string을 활용하고 있고 두번의 반복문으로 문자를 대체 한다(자세한 내용은 솔루션 링크 참고) + +너무 한방향만 진행하였을 경우만 생각하게 되어서 2번같은 결과가 나온것 같다 솔루션을 보면서 대단하다는 생각이 들었다. + +
+
+ + +### min stack + +push,pop,top을 구현하는 문제이다 + +getmin을 구하는것도 포함되어있다 하지만 constant time으로 min val을 알아내야 한다 + + + +스택을 2개 사용해서 min을 위한 스택을 사용해서 해결하는 과정으로 갈것이다 + +조금더 최적화를 한다고 하면 min을 위한 스택에 요소값이 더 작지 않다면 구지 업데이트 하지 않아도 된다 -> pair로 해결도 가능하다 반복되는 요소를 없에주기 위함이다 + +시간 : O(1) +공간 : O(N) + + + +```c++ + +class MinStack { +public: + vector stack; + vector> min_stack; + MinStack() + { + vector stack={}; + vector> min_stack={}; + } + + void push(int val) + { + stack.emplace_back(val); + + if(!min_stack.empty()) + { + if(min_stack.back().first==val) + { + min_stack.back().second++; + + } + else if(min_stack.back().first>val) + { + min_stack.emplace_back(make_pair(val,1)); + } + return; + } + min_stack.emplace_back(make_pair(val,1)); + } + + void pop() + { + //!stack.empty()&& + if(stack.back()!=min_stack.back().first) + { + + stack.pop_back(); + return; + + } + //!stack.empty()&&!min_stack.empty()&& + else if(stack.back()==min_stack.back().first) + { + min_stack.back().second--; + + if(min_stack.back().second==0) + { + + min_stack.pop_back(); + stack.pop_back(); + + + } + else + { + stack.pop_back(); + } + + return; + } + + } + + int top() + { + if(!stack.empty()) + { + return stack.back(); + } + return 0; + } + + int getMin() + { + return min_stack.back().first; + } +}; + +/** + * Your MinStack object will be instantiated and called as such: + * MinStack* obj = new MinStack(); + * obj->push(val); + * obj->pop(); + * int param_3 = obj->top(); + * int param_4 = obj->getMin(); + */ +``` + +처음에는 주석에 있는 조건을 넣었는데 생각보다 느려서 빼고 돌렸더니 더 빠르게 나왔다 위에서 설명한 방식대로 구현을 하였는데 막혔던 부분은 pair +second가 0이 아닌경우에는 stack에서 pop을 해주어야 하는데 그 부분을 생각을 못해서 한참을 방황하다가 찾아서 제출을 하였다 어쨌든 핵심은 min stack을 따로 만들어서 해결하는게 핵심이고 최적화 부분이라서 공간 복잡도는 똑같이 O(N)이다 + + +--------- + +### 1047. Remove All Adjacent Duplicates In String + +중복된 문자열이 있으면 제거 한 후 반환하는 문제 + +단순히 포인터로 해결하려고 하면 이전 포인터 요소와 현재 포인터 요소와 비교 할수가 있다 그리고 다른 문자열을 넣든가 공백을 넣어서 진행을 하고 후 처리로 공백/대체 문자를 제거 하는 방식을 거치고 반환 하는 방법이 있다 하지만 좋지 않다 +그래서 stack으로 접근을 해보자 스택으로 쌓다가 반복된 문자가 있으면 pop해주면 된다 + + +```c++ + +class Solution { +public: + string removeDuplicates(string s) + { + string result=""; + result+=s[0]; //empty상태로 시작하지 않기 위함이다 + + for(int i=1;i> comp; + + for (int i = 0; i < s.size(); i++) + { + if (!comp.empty() && comp.back().first == s[i] && comp.back().second == k - 1) + { + comp.pop_back(); + continue; + } + else if (!comp.empty()&&comp.back().first==s[i]) + { + comp.back().second++; + continue; + } + comp.emplace_back(s[i], 1); + } + + for (auto& e : comp) + { + result += string(e.second, e.first); + } + + return result; +} +}; + + +``` + +처음에는 위에서 제시 한것 처럼 스택 공간을 따로 만들어서 처리 하려고 했는데 풀다 보니까 pair로 가져가면 따로 스택 공간을 만들 필요는 없는것 같다! 다만 k에 따라서 char형 반복해서 가져 가야 하니까 string(int,char)이거 알아두면 좋을것 같다 파이썬에서 's'*5==sssss와 같은 기능이다 + + + + + + + + +**전반적인 생각** +stack 파트라서 생각하기 상대적으로 쉬웠을수 있다 하지만 만약 이런 힌트 없었다면 +생각 하기 쉽지 않았을 것이다 array,string문제 생각 할때 two pointer,sliding ,binary search이외에도 stack으로 한번 생각 할 필요가 있다 그리고 stack으로 접근해서 풀때 stack공간을 한번 더 만들어서 생각하는것도 큰 도움이 된다 + + +---------- + +### Decode string + +이 문제에서 어려운건 괄호가 중복이 되어있다는거고 그 중복된 문자열까지 처리를 해야 한다는게 어렵다. + + + +두가지 해결 방법을 가져왔다 제출결과 시간 복잡도는 거의 동일했다 이해하기 쉬운 코드 부터 순서대로 제시 하겠다 + +첫 번째 방법은 ] 가 나올때까지 result string에 결과를 받고 ]가 나오면 이때 부터 처리를 한다 우선 [가 나올때까지 substr에 저장을 한다 그리고 [ 없에준다 +그러면 앞에는 이제 숫자 k가 나올것인데 k가 한자리가 아니라 세자리 네자리등으로 확장 될수있으므로 우선은 문자열로 숫자를 받는다 +그리고 나서 atoi를 사용해서 문자열 숫자를 정수형 숫자로 바꾸고 나서 substr을 반복한다 +반복한 결과를 다시 result에 더해준다 + + + +```c++ +class Solution { +public: + string decodeString(string s) + { + string result=""; + for(int i=0;i str_stack; + stack num_stack; + + string cur_str = ""; + string temp = ""; + int num = 0; + + for (auto& e : s) + { + if (e == '[') + { + str_stack.push(cur_str); + num_stack.push(num); + cur_str = ""; + num = 0; + continue; + } + else if (e == ']') + { + string prev = str_stack.top(); + str_stack.pop(); + int prev_num = num_stack.top(); + num_stack.pop(); + temp = ""; + for (int i = 0; i < prev_num; i++) + { + temp += cur_str; + } + cur_str = prev + temp; + continue; + + } + + if (isdigit(e)) + { + num = num * 10 + (e-'0'); + } + else + { + cur_str += e; + } + + } + + return cur_str; + + } +}; +``` \ No newline at end of file diff --git a/_posts/2023-07-19-hash_problem.md b/_posts/2023-07-19-hash_problem.md new file mode 100644 index 000000000000..fae875da7185 --- /dev/null +++ b/_posts/2023-07-19-hash_problem.md @@ -0,0 +1,322 @@ +--- +layout: single +title: "hash 개념 및 문제" +categories: [c++,set,hash,map,hash function,hash problem,algorithm,hash table] +tag : [c++] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + + +### hash 개념 + +set,map 자료 구조 정리한 포스팅 있는데 참고하면 좋다! + + +hash_value / bucket count ==> hash table의 index가 나온다 + +삽입,탐색,삭제 ==> O(1) time complexity를 가진다 + +hash table의 value는 연결리스트로 이어져 있는데(같은 hash값 나오면 연결리스트에 추가가 된다) 그래서 최악의 경우 탐색 시간이 O(n)될수있다 연결리스트를 탐색하기 때문이다 +vector에서도 강조 되었지만 메모리가 낭비되지 않는선에서 reserve하는게 중요하다 -> rehashing 현상을 막아야 한다 + +
+
+ +좋은 hash는 메모리 낭비 하지 않고 충돌이 최소가 되는것이다 + + + + +### hash 문제 리스트 + +[two-sum](https://leetcode.com/problems/two-sum/description/) + +[find first unique char](https://leetcode.com/problems/first-unique-character-in-a-string/) + +[isomophic string](https://leetcode.com/problems/isomorphic-strings/) + +[valid anagram](https://leetcode.com/problems/valid-anagram/) + +[world pattern](https://leetcode.com/problems/word-pattern/) + +____ + + +### hashmap second value기준으로 찾고 싶을때 + +find_if를 사용하면 된다 + +```c++ +std::unordered_map myMap = {{"apple", 1}, {"banana", 2}, {"cherry", 3}}; +auto it = myMap.find("banana"); +if (it != myMap.end()) { + std::cout << "The value of the key 'banana' is " << it->second << std::endl; +} else { + std::cout << "The key 'banana' is not found in the map" << std::endl; +} + + +``` +
+
+ +하지만 대부분의 코테에서는 c20을 지원하지 않기 때문에 실제로 풀려면 hashmap을 한개 더 만들어서 찾아야 한다!! + +
+
+ + +또한 해쉬 맵에서 유용한 기술은 탐색 삽입이 강점인 만큼 어떤 합의 존재를 찾을때 유용하게 사용할수가 있다 + +------------------------- +### two sum - hash version + +two sum문제는 array 문제에서 풀었던 문제이다 hash table을 사용해서 O(n)으로 풀수가 있다 코드는 다음과 같다 + + +```c++ +class Solution +{ +public: + vector twoSum(vector& nums, int target) + { + unordered_map hash; + + for (int i = 0; i < nums.size(); i++) + { + if (hash.find(nums[i]) != hash.end()) + { + return { hash[nums[i]],i }; + + } + hash.emplace(target - nums[i], i); + } + return {}; + + } +}; + +``` + + + +-> hash table key에 target-nums[i]를 넣어서 필요한 숫자가 nums 배열 요소로 나오면 바로 return하는 구조로 문제를 풀수가 있다 + + + +_____ + +### two sum 변형 문제 + +two sum과 비슷한 문제인데 쌍이 몇개 있는지 반환하는 문제이다 얼핏 보면 위에와 같은 문제인것 처럼 보이지만 약간 다르다 +예를 들어 {3,3,3,3,3}, k=6이면 정답은 10개이다 즉 hashmap에서 k-num[i]가 있는지 없는지 유무에 따라 판단하게 되면 1번밖에 적용되지 않는다는 문제가 있다 그래서 이를 해결하기 위해 몇개가 있는지 확인할 필요가 있는것이다 +
+
+ +```c++ +#include +#include +using namespace std; + + +int main() { + // 여기에 코드를 작성해주세요. + ios::sync_with_stdio(false); + cin.tie(NULL); + unordered_map map; + + int n=0,k=0,cnt=0; + cin>>n>>k; + + for(int i=0;i>temp; + long long diff =(long long)k-temp; + cnt+=map[diff]; + map[temp]++; + } + cout< hash_map; + + for (auto& ele : s) + { + + + hash_map[ele]++; + + + } + + for (int i=0;i temp; + unordered_map temp2; + + for (int i = 0; i < s.size(); i++) + { + if (temp.find(s[i]) != temp.end() ) + { + if (t[i] != temp[s[i]]) + { + return false; + } + } + if (temp2.find(t[i]) != temp2.end()) + { + if (s[i] != temp2[t[i]]) + { + return false; + } + } + temp[s[i]] = t[i]; + temp2[t[i]] = s[i]; + + } + return true; + + + } +}; +``` + + +----------- + + +### valid anagram + +s,t 문자열이 anagram인지 판단하는 문제이다 + +```c++ +class Solution { +public: + bool isAnagram(string s, string t) { + + unordered_map compare; + + for (auto& ele : s) + { + compare[ele]++; + + } + + for (auto& ele : t) + { + if (compare.find(ele) != compare.end()) + { + compare[ele]--; + + } + else + { + return false; + } + } + + for (auto& ele : s) + { + if (compare[ele] != 0) + { + return false; + } + } + return true; + + } +}; +``` + +-------------------------------- + + +### world pattern + +공백을 처리 하기 위해 string stream을 사용해 주었다 그리고 핵심은 양방향으로 패턴이 대응 되어야 하기 때문에 위에서 풀었던 문제 처럼 2개의 해쉬맵을 사용한다 패턴이 맞아야 하기 때문에 s가 남거나 t가 남는 상황이 있다면 바로 false를 반환한다 + +
+
+ + +```c++ +class Solution +{ +public: + bool wordPattern(string pattern, string s) + { + stringstream os(s); + string token; + unordered_map m1; + unordered_map m2; + + for (int i = 0; i < pattern.size(); i++) + { + + os >> token; + + + m1.emplace(pattern[i],token); + m2.emplace(token, pattern[i]); + if (pattern[i] != m2[token] || token!= m1[pattern[i]]) + { + return false; + } + } + + if (m1.size() != m2.size()||os>>token) + { + return false; + } + + + return true; + + + + } +}; +``` + diff --git a/_posts/2023-08-04-shoten_time_technique.md b/_posts/2023-08-04-shoten_time_technique.md new file mode 100644 index 000000000000..4c62a1114535 --- /dev/null +++ b/_posts/2023-08-04-shoten_time_technique.md @@ -0,0 +1,511 @@ +--- +layout: single +title: "shoten time technique" +categories: [prefix sum, Grid compression,LR technique,+1-1 techique,two pointer, 전처리] +tag : [c++,python,Algorithm,LeetCode,Heap,Kth Largest Element in an Array,Top K Frequent Elements,Merge k Sorted Lists ,Find the Kth Largest Integer in the Array,디스크 컨트롤러] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + + + +### intro + +코드 트리 알고리즘 기본 과정 중에서 shoten time technique에 해당하는 내용입니다 + +
+
+ + +### prefix sum (구간합) + +구간합을 만드는데 시간 : O(N)이다 +하지만 만든 이후 [a,b]구간의 합을 구할수가 있다 **S_(b) - S_(a-1)**으로 [a,b]구간의 합을 구할수가 있다 이때 S_0인 경우를 고려 하여야 하기때문에 초반에 0으로 설정 하여야 한다 - 우선 순위 큐 파트 부분 문제에서도 사용한 적이 있는데 실제로 다양하게 활용이 되고 있다 + + + +
+
+ +(코드트리 정수 n개의 합_2)[https://www.codetree.ai/missions/8/problems/sum-of-n-integers-2?utm_source=clipboard&utm_medium=text] + +
+
+ +n,k가 입력으로 주어질때 연속하는 k개의 합중 가장 큰 값을 출력하는 문제이다 최대 값을 구하는 문제니까 구간합 구간을 [i,i+k]로 설정 하면 된다 그리고 배운점이 N개의 사이즈 안에 i+k-1이 존재 해야 하니까 i의 반복문 조건을 구할수가 있다 (n-k+1) 좌변에 i만 남기고 우변으로 모두 이항한다 + +```c++ +#include +#include + +using namespace std; + +int main() +{ + vectorprefix_sum; + vectorarr; + prefix_sum.reserve(100000); + arr.reserve(100000); + int n=0,k=0; + cin>>n>>k; + for(int i=0;i>t; + arr.emplace_back(t); + } + + prefix_sum[0] = 0; + for(int i = 1; i <= n; i++) + prefix_sum[i] = prefix_sum[i - 1] + arr[i]; + + int max_val = -10000000000; + + //구간 범위 구하는 부분 + for(int i=1;i<=n-k+1;i++) + { + max_val=max(max_val,prefix_sum[i+k-1]-prefix_sum[i-1]); + } + cout< +
+ +(코드트리 정수 n개의 합3)[https://www.codetree.ai/missions/8/problems/sum-of-n-integers-3?utm_source=clipboard&utm_medium=text] + +
+
+ +이번에는 2차원에서 구간합을 살펴 볼것이다 2차원에서 구간합 만드는 시간 O(N^2)이다 하지만 만들고 난 이후 구간합 배열 기반으로 넓이 구하는 부분은 O(1)이면 해결 가능하다는 장점이 있다 + +위에 문제와 거의 동일하게 진행하면 된다 위에서 i의 구간을 구했던 것 처럼 2차원에서도 똑같이 i와 j에 대해 k개의 정사각형 넓이에 해당하는 범위를 설정 해야 한다 + +1. 구간합 만들기 -> 2차원 벡터 0으로 초기화 후에 **S(a,b) = S(a-1,b) + S(a,b-1) - S(a-1,b-1)+ A(a,b)** 공식을 사용해서 만들어 준다 + +2. 만든 구간합 기반으로 k*k의 정사각형 넓이 중 최대 구하기 - convolutiond에서 kernel의 이동으로 생각하면 된다 + + **S(x2,y2)-S(x1-1,y2)-S(x2,y1-1)+S(x1-1,y1-1)** + + + + + + +```c++ +#include +#include +using namespace std; + +//구간합을 이용해서 넓이 구하는 함수 +int two_D_prefix_sum(int x1,int y1,int x2,int y2,vector>prefix_sum) +{ + return prefix_sum[x2][y2]-prefix_sum[x1-1][y2]-prefix_sum[x2][y1-1]+prefix_sum[x1-1][y1-1]; +} + +int main() +{ + // 여기에 코드를 작성해주세요. + int n=0,k=0; + cin>>n>>k; + vector> vec(n+1,vector(n+1,0)); //n*n 2차원 벡터 설정 + vector> prefix_sum(n+1,vector(n+1,0)); //n*n 2차원 벡터 설정 + + for(int i=1;i<=n;i++) + { + for(int j=1;j<=n;j++) + { + int t=0; + cin>>t; + vec[i][j]=t; + } + } + //구간합 만들기 + for(int i=1;i<=n;i++) + { + for(int j=1;j<=n;j++) + { + + prefix_sum[i][j]=prefix_sum[i-1][j]+prefix_sum[i][j-1]-prefix_sum[i-1][j-1]+vec[i][j]; + + } + + } + int max_val = -100000000; + for(int i=1;i<=n-k+1;i++) + { + for(int j =1;j<=n-k+1;j++) + { + max_val=max(max_val,two_D_prefix_sum(i,j,i+k-1,j+k-1,prefix_sum)); + } + } + + cout< +
+ + +(코드 트리 문제 : subarray sum equal k)[https://www.codetree.ai/missions/8/problems/the-sum-of-the-subsequences-is-k?utm_source=clipboard&utm_medium=text] + + +
+
+ +이 문제같은 경우 요소값이 모두 양수이기 때문에 sliding window로 해도 문제가 풀리긴 하지만 이 문제에서는 시간 제한이 있기 때문에 sliding window말고 다르게 풀어야 한다 구간합을 이용해서 문제를 해결해야 하는데 +내가 푼 방식을 설명하면 다음과 같다. 누적값과 unordered_map을 활용할것이다 +우선 누적합을 map key에 저장을 할것이다 만약 중복된 값이면 ++를 통해 개수를 증가 해줄것이다 그리고나서 key-target의 값을 map에서 찾는다 만약 있다면 cnt를 증가 시킨다 + +```c++ + +#include +#include +using namespace std; + +int main() +{ + int n=0,k=0; + int cnt=0,sum=0; + unordered_map map; + + //0초기화 + map[0]=1; + cin>>n>>k; + + for(int i=0;i>t; + sum+=t; + if(map.find(sum-k)!=map.end()) + { + cnt+=map[sum-k]; + } + map[sum]++; + } + cout< +
+ + +(코드트리 마라톤 중간에 택시타기)[https://www.codetree.ai/missions/8/problems/taking-a-taxi-in-the-middle-of-the-marathon?utm_source=clipboard&utm_medium=text] + +
+
+ +이 문제를 보면 위에서 말한 내용이 무엇인지 조금 감을 잡을수있을것 같다 개념 설명 할때는 인접한 요소의 차이값으로 L,R을 채워나갔지만 이 문제에서는 맨하튼 거리계산값을 넣어야 한다 그리고 LR technique을 모르고 문제를 보면 모든 요소를 반복문으로 돌면서 맨하튼 거리 계산을 해야 할것이다 하지만 LR을 사용하면 더 적은 시간 복잡도를 사용할수가 있다 + +문제에서 포인트 하나를 건너띄었을때 맨하튼 거리 값이 최소인 값을 반환하는 문제이다 건너띄는 포인트를 i라고 할때 L은 0~i-1까지이고 R은 i+1~n-1까지로 배열 인덱스를 설정할수가있다 그리고 i를 건너띄니까 i-1~i+1까지 맨하튼 거리 값을 더해주면 최종적으로 i를 건너띈 맨하튼 거리를 구할수가 있다 이를 코드로 나타내면 아래와 같다 + +
+
+ +```c++ +#include +#include +#include +using namespace std; + +int main() +{ + int n=0; + cin>>n; + + vector> arr; + + //LR 배열 선언 + vector L(n,0); + vector R(n,0); + + for(int i=0;i>j>>k; + arr.emplace_back(make_pair(j,k)); + + } + + //L + for(int i=1;i=1;i--) + { + R[i]=R[i+1]+abs(arr[i+1].first-arr[i].first)+abs(arr[i+1].second-arr[i].second); + } + int ans = 10000000000; + //모든 요소를 건너띄는 시뮬레이션 돌릴때 최소값 구하기 + for(int i=0;i +
+ +(코드트리 가장 많이 겹치는 구간)[https://www.codetree.ai/missions/8/problems/section-with-maximum-overlap?utm_source=clipboard&utm_medium=text] + +
+
+ +```c++ +#include +#include +#include +#include +using namespace std; +int main() +{ + // 여기에 코드를 작성해주세요. + + int n=0; + vector> v; + + cin>>n; + for(int i=0;i>x>>y; + v.emplace_back(make_pair(x,1)); + v.emplace_back(make_pair(y,-1)); + + + + } + + //x값 기준 정렬 + sort(v.begin(),v.end()); + + + int sum_val=0; + int max_val = 0; + + for(int i=0;i<2*n;i++) + { + sum_val+=v[i].second; + max_val=max(max_val,sum_val); + + + } + cout< +
+ + +(코드트리 서로다른 구간의 수)[https://www.codetree.ai/missions/8/problems/number-of-distinct-segments?utm_source=clipboard&utm_medium=text] + +
+
+ +모든 구간을 합친 이후 남아있는 서로 다른 구간의 수를 구하는 문제이다 +이 문제를 풀기 위해서 +-1 기술을 사용할것인데 구간을 합치려면 우선 겹치는 구간을 알아야 하기 때문에 x값 기준 정렬할것이다 그리고 나서 시작점 즉 +1값을 unordered_set에 삽입한다 그리고 끝점 -1이 나오면 set에서 삭제를 해줌으로써 같은 구간의 선분을 통합한다 이때 set의 사이즈가 0이면 새로운 구간이라는 의미 이므로 ans++하면 된다 + + +```c++ +#include +#include +#include +#include +#include + +using namespace std; + +int main() +{ + + + //세쌍의 값을 받기 위해 tuple사용 + vector > vec; + int n=0; + cin>>n; + + for(int i = 0; i < n; i++) { + int x=0, y=0; + cin>>x>>y; + + + + //(x좌표, +1-1값, 선분 번호) + vec.emplace_back(make_tuple(x, +1, i)); + vec.emplace_back(make_tuple(y, -1, i)); + } + + + sort(vec.begin(), vec.end()); + + + unordered_set set; + + int ans = 0; + for(int i = 0; i < 2 * n; i++) + { + + // tuple 값 접근은 get으로 한다 + + // + if(get<1>(vec[i]) == +1) + { + //새로운 구간 + if((int) set.size() == 0) + ans++; + + + set.emplace(get<2>(vec[i])); + } + + // -1 즉 끝점 + else + { + + set.erase(get<2>(vec[i])); + } + } + + + cout << ans; + return 0; +} + + +``` + +세쌍을 받기 위해 tuple을 사용했다 tuple값 접근 하는 방법 make_tuple하는 법 코드에 나와있으니 필요할때 찾아보면 좋을것 같다 + + + +### 전처리 + + +brute force 방식으로 풀었을때 시간 초과 나는 문제를 전처리를 통해서 시간 초과 안나게 하는 방식인것 같다. 대표적으로 이중 포문을 돌아야 하는 상황이 많은것 같은데 그 과정에서 필요한게 무엇인지 파악해서 전처리를 한다면 시간을 줄일수있다는 전략이다. + +
+
+ +(괄호쌍 만들기)[https://www.codetree.ai/missions/8/problems/pair-parentheses?utm_source=clipboard&utm_medium=text] + +
+
+ +위에 문제도 ((이 나올때 ))이 나오는 부분을 세주면 이중 반복문으로 문제를 해결할수있지만 시간 초과가 나는 문제가 있다 이 문제에서 전처리를 어떻게 해줄것인지 살펴보자 + +우선 이중 반복문을 사용해서 문제를 푸는 경우의 코드 부터 살펴 보자 +```c++ +#include +int main() +{ + string s = ")((()())())"; + int ans =0; + for(int i=0;i +#include +#include +using namespace std; +int main() +{ + string str; + cin>>str; + vector R(str.size(),0); //0으로 초기화 + + //연속하는 닫는 괄호 개수 세는 배열 만들기 + for(long long int i=str.size()-2;i>-1;i--) + { + //연속된 닫는 괄호의 경우 + if(str[i]==')'&&str[i+1]==')') + { + R[i]=R[i+1]+1; + } + else + { + R[i]=R[i+1]; + } + } + long long int ans=0; + for(long long int i=0;i +
+ +1. 데이터 삽입/삭제 -> O(1) + + 삽입 할때는 새로운 노드의 next를 이전 노드의 next값으로 설정한 후 이전 노드의 next를 갱신한다 + + +2. 탐색 -> O(N) + +
+
+ + +### singled linked list + +가장 기본적인 한방향 연결 리스트이다 삽입 삭제 탐색까지 구현 한 코드는 다음과 같다 + + + +```c++ + +#include + +using namespace std; + +template //for all data type + +struct Node +{ + T value; + struct Node *next = nullptr; + +}; + +class Singled_linked_list +{ + Node *head; + Node *tail; + +public: + Singled_linked_list() + :head(nullptr),tail(nullptr) + { + + } + ~Singled_linked_list() + { + delete head; + delete tail; + } + + void add_node(T _value) + { + Node *new_node = new Node; + + node->value = _value; + node->next = nullptr; + + if(head==nullptr) + { + head = node; + } + else + { + tail->next = node; + } + tail = node; + + } + + void display() + { + Node *temp = new Node; + temp = head; + while(temp!= nullptr) + { + cout<value<next; + } + + } + + void insert_front(T _value) + { + Node *temp = new Node; + + temp->value = _value; + temp->next = head; + head = temp; + } + void insert_position(const int &pos,T _value) + { + Node *pre = new Node; + Node *cur = new Node; + Node *temp = new Node; + + cur = head; + + for(int i=1;inext; + + } + temp->value = _value; + pre->next = temp; + temp->next = cur; + + } + void delete_first() + { + assert(head!=nullptr); + + Node *temp = new Node; + temp = head; + head = head->next; + delete temp; + //사실 temp만들고 지울필요는 없다 그냥 head=head->next만 해도 첫번째 지우는 효과 있다 + + } + + void delete_last() + { + Node *pre = new Node; + Node *cur = new Node; + + + cur = head; + + //배열처럼 kth접근 안됨 탐색 해야 한다 + while(cur->next != nullptr) + { + pre = cur; + cur = cur->next; + } + tail = pre; + pre->next = nullptr; + delete cur; + + } + void delete_position(const int &pos) + { + Node *pre = new Node; + Node *cur = new Node; + + cur = head; + for(int i = 1;i < pos;i++) + { + pre = cur; + cur = cur->next; + } + pre->next = cur->next; + delete cur; + + } + /* + + 두 노드를 swap하려면 이전 노드를 두 노드 요소와 이어주어야 하고 현재 노드의 next를 또 한번 이어주어야 한다 + 이 두과정을 해야 한다 + + */ + //node끼리 swap하기 + void swap_node(Node** head,T val1,T val2) + { + if(val1==val2) + { + return; + } + Node *prev_val1=nullptr; + Node *cur_val1= *head; + + //탐색해서 val1 노드 찾기 + while(cur_val1&&cur_val->value!=val1) + { + prev_val1=cur_val1; + cur_val1 = cur_val1->next; + + } + //탐색 해서 val2 노드 찾기 + + Node *prev_val2 = nullptr; + Node *cur_val2 = *head; + while(cur_val2&&cur_val2->value!=val2) + { + prev_val2 = cur_val2; + cur_val2 = cur_val2->next; + } + + //만약 둘중에 하나라도 null이면 할수있는게 없으므로 반환 + + if(cur_val1==NULL || cur_val2==NULL) + { + return; + } + + //만약 val1이 head라면 prev_val1은 null일것이다 이때는 val2를 head로 만들어야 한다 + + if(prev_val1!=NULL) + { + prev_val1->next = cur_val2; + } + else + { + *head = cur_val2; + } + + //val2에 대해서도 똑같이 적용 + + if(prev_val2!=NULL) + { + prev_val2->next = cur_val1; + } + else + { + *head = cur_val1; + } + + //cur의 next를 또 이어주는 작업 + + Node* temp = cur_val2->next; + + cur_val2->next = cur_val1->next; + cur_val1->next = temp; + + + + + } +}; +int main() +{ + return 0; +} +``` + +
+
+ +### remove linked list element leetcode + +요소를 지우는 간단한 문제이다 하지만 삭제할 대상이 head인 상황이면 pre node가 필요하기 때문에 dummy node개념을 도입해서 문제 해결 한다 코드는 다음과 같다 + + +Input: head = [1,2,6,3,4,5,6], val = 6 +Output: [1,2,3,4,5] + + + +```c++ + +#include +using namespace std; +struct ListNode +{ + int val=0; + ListNode* next = nullptr; + + ListNode(int x) + :val(x),next(nullptr) + {} + +}; +ListNode* removeElements(ListNode* head,int val) +{ + ListNode* dummy = new ListNode; + ListNode* cur = new ListNode; + ListNode* pre = new ListNode; + ListNode* temp = new ListNode; + + //dummy node + + dummy->next = head; + cur = head; + pre = dummy; + + while(cur!=nullptr) + { + if(cur->val ==val) + { + pre->next = cur->next; + temp = cur; + cur = cur->next; + delete temp; + continue; + } + pre = cur; + cur = cur->next; + } + + return dummy->next;//dummy 개념 다시 원상 복구 + + +} +int main() +{ + +} +``` + +### merge two sorted linked list leetcode + + + + + +**iterator verstion** + +
+
+ +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode() : val(0), next(nullptr) {} + * ListNode(int x) : val(x), next(nullptr) {} + * ListNode(int x, ListNode *next) : val(x), next(next) {} + * }; + */ +class Solution +{ +public: + ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) + { + ListNode *dummy = new ListNode; + ListNode *cur = new ListNode; + ListNode *node1 = new ListNode; + ListNode *node2 = new ListNode; + + + cur = dummy; + + node1 = list1; + node2 = list2; + + while(node1!=nullptr || node2!= nullptr) + { + if(node1==nullptr) + { + cur->next = node2; + break; + } + else if(node2 == nullptr) + { + cur->next = node1; + break; + + } + if(node1->val>=node2->val) + { + cur->next = node2; + cur = node2; + node2 = node2->next; + } + else + { + cur->next = node1; + cur = node1; + node1 = node1->next; + } + } + + return dummy->next; + + + + } +}; +``` + +**recursive verstion** + +
+
+ +```c++ + + +``` + + +### middle-of-the-linked-list leetcode + +이 문제 같은 경우 연결 리스트의 단점을 명확히 알수있는 문제이다 배열이면 kth요소 바로 반환 힐수있다 왜냐 하면 배열의 크기를 알수있기 때문이다 하지만 연결리스트의 경우 크기를 알아내려면 탐색을 끝까지 해야 하기 때문에 사이즈를 구할때 O(N)이 소요 된다 + +이 문제를 푸는 3가지 방법을 소개 한다 + +1. 탐색을 하여서 size알아내고 size/2 지점에서 반환하는 방법 + + 가장 직관적이면서도 여전히 좋은 방법이다 time : O(N), space O(1) + +2. 1번은 탐색을 두번 진행을 한다 만약 one pass로만 구현해주라고 한다면 바로 배열처럼 전환을 하면 된다 + + one pass할때 배열로 복사를 한다 배열로 복사 하면 바로 중간 지점을 반환 할수가 있다 time : O(N), space : O(N) + +3. fast slow 방식 + + fast 노드와 slow 노드를 설정 한다 fast 노드는 2칸을 이동할때 slow노드는 한칸을 이동한다 fast가 null 또는 fast -> next가 null인 경우 멈춘다 이때 slow노드 지점이 mid of linked list의 정답이 된다 이 알고리즘으로 문제를 풀면 one pass성질 만족하면서 time : O(N), space : O(1)을 만족할수있다 + + +이번 문제에서 보아야 하는건 문제 해설이 아니라 생각의 과정이다 인터뷰를 진행 한다고 하면 반드시 위와 같이 제시가 될것이다 + + +**fast slow point** + +
+
+ +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode() : val(0), next(nullptr) {} + * ListNode(int x) : val(x), next(nullptr) {} + * ListNode(int x, ListNode *next) : val(x), next(next) {} + * }; + */ +class Solution +{ +public: + + ListNode* middleNode(ListNode* head) + { + ListNode* fast, * slow; + fast = head; + slow = head; + + while (fast != NULL && fast->next != NULL) + { + fast = fast->next->next; //두칸 이동 + slow = slow->next;//한칸 이동 + } + return slow; + } + +}; + +``` + +### sort list leetcode + + +문제 : [sort list problem ](https://leetcode.com/problems/sort-list/description/) + +
+
+ +처음에는 O(N)만큼 탐색하면서 vector에 옮기고 stl sort를 사용해서 해결하려고 했지만 문제에서 요구하는 space complexity를 만족하지 않기 때문에 다른 방법을 고려 하였다 그래서 merge sort를 linked list에 맞게 구현하고 해결하려 했지만 heap sort로 해도 괜찮을것 같아서 heap sort를 linked list에 맞게 변경하기로 잡고 문제를 풀려고 했다 그러기 위해서 min heap을 구현하기로 했다 부모노드와 자식 노드간의 관계식을 이용해서 그 요소의 값을 접근하기 위해 element라는 함수를 만들었다 해당 인덱스만큼 탐색해서 만족하는 노드를 return 하는 함수이다 그리고 그 요소의 값과 비교해서 min 값을 갱신한다 마지막에 min!=rootidx의 경우 swap하기 위해 linked list에 맞게 swap하는 함수도 구현을 했다 +linked list swap코드는 singledl linked list에 언급 + +min heap까지 알맞게 구현 하였는데 이제 원래 heap sort 처럼 pop을 하면서 sorting 하는 방식인데 pop까지 구현 하기가 어려워서 포기 했다 아래는 min heap을 linked list에 맞게 작성한 코드이고 출력해보면 min heap작동은 하는것 같다 + + + + +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode() : val(0), next(nullptr) {} + * ListNode(int x) : val(x), next(nullptr) {} + * ListNode(int x, ListNode *next) : val(x), next(next) {} + * }; + + +O(n)만큼 이동해서 벡터에 저장 하고 난후 아 이러면 space가 on이네 + + + */ +class Solution +{ +public: + void swap_node(ListNode **head,int val1,int val2) + { + if(val1==val2) + { + return; + } + ListNode *prev_val1=nullptr; + ListNode *cur_val1 = *head; + + while(cur_val1&&cur_val1->val!=val1) + { + prev_val1=cur_val1; + cur_val1=cur_val1->next; + } + ListNode *prev_val2=nullptr; + ListNode *cur_val2 = *head; + + while(cur_val2&&cur_val2->val!=val2) + { + prev_val2 = cur_val2; + cur_val2 = cur_val2->next; + + } + if(cur_val1==NULL || cur_val2==NULL) + { + return; + } + + if(prev_val1!=NULL) + { + prev_val1->next= cur_val2; + } + else + { + *head = cur_val2; + } + + if(prev_val2!=NULL) + { + prev_val2->next= cur_val1; + } + else + { + *head = cur_val1; + } + ListNode *temp = cur_val2->next; + cur_val2->next = cur_val1->next; + cur_val1->next = temp; + std::cout<<" swap"<next; + idx++; + } + return t; + } + void min_heapify(ListNode* head,int rootidx,int &size) + { + int Min = rootidx; + int left = 2*rootidx+1; + int right = 2*rootidx+2; + + ListNode *left_node = element(head,left); + ListNode *right_node = element(head,right); + ListNode *min_node = element(head,Min); + ListNode *rootidx_node = element(head,rootidx); + + ListNode *rr=new ListNode; + + if(leftval < min_node->val) + { + Min = left; + std::cout<<"left"<valval) + { + Min = right; + std::cout<<"right"<val,rootidx_node->val); + std::cout<val<<" "<val<val<next; + } + + min_heapify(head,Min,size); + } + //return head; + + } + ListNode* sortList(ListNode* head) + { + ListNode *temp = new ListNode; + ListNode *tt = new ListNode; + ListNode *result = new ListNode; + + temp=head; + result = head; + tt=head; + int size = 0; + + while(temp!=nullptr) + { + temp=temp->next; + size++; + } + //ListNode *cur = element(tt,size/2); + + for(int i = size/2;i>-1;i--) + { + + + std::cout<<"DDD "< +
+ +```c++ +class Solution { +public: + ListNode* midpoint(ListNode* head){ + if(head == NULL or head->next == NULL){ + return head; + } + + ListNode *slow = head, *fast = head->next; + while(fast && fast->next){ + slow = slow->next; + fast = fast->next->next; + } + + return slow; + } + + ListNode* mergeSortedLists(ListNode* a, ListNode* b){ + if(a == NULL) return b; + if(b == NULL) return a; + + ListNode* temp; + if(a->val <= b->val){ + temp = a; + temp->next = mergeSortedLists(a->next, b); + } + else{ + temp = b; + temp->next = mergeSortedLists(a, b->next); + } + + return temp; + } + + ListNode* sortList(ListNode* head) { + if(head == NULL or head->next == NULL){ + return head; + } + + ListNode* mid = midpoint(head); + ListNode* a = head; + ListNode* b = mid->next; + + mid->next = NULL; + + a = sortList(a); + b = sortList(b); + + ListNode* temp = mergeSortedLists(a, b); + return temp; + } +}; +``` +fast slow 방식을 사용해서 mid point를 찾았다 + +
+
+ + +### swap nodes in pairs leetcode + +[swap node in linked list](https://leetcode.com/problems/swap-nodes-in-pairs/) + +위에서 node 요소 스왑 하는거에는 큰 오류가 있다 만약 같은 값이 먼저 나와 버리면 swap하려는 대상을 잘못 swap할수있다 왜냐하면 값으로만 판단하기 때문이다 그래서 이를 해결하려고 코드를 수정해 보았지만 막혀서 결국 solution을 참고 하였다 아래 코드는 solution참고 하기전에 구현한 코드이다 + + +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode() : val(0), next(nullptr) {} + * ListNode(int x) : val(x), next(nullptr) {} + * ListNode(int x, ListNode *next) : val(x), next(next) {} + * }; + */ +class Solution +{ +public: + void swap_node(ListNode**head,int val1,int val2,int i) + { + int cnt=0; + if(val1==val2) + { + return; + } + ListNode *prev_val1 = nullptr; + ListNode *cur_val1 = *head; + while(true) + { + while(cur_val1&&cur_val1->val!=val1) + { + prev_val1 = cur_val1; + cur_val1 = cur_val1->next; + cnt++; + //std::cout<<"val1 "<val<<" "<next; + continue; + } + else + { + break; + } + } + + + + ListNode *prev_val2 = nullptr; + ListNode *cur_val2 = *head; + cnt=0; + while(true) + { + while(cur_val2&&cur_val2->val!=val2) + { + prev_val2 = cur_val2; + cur_val2 = cur_val2->next; + cnt++; + + std::cout<<"val2 "<val<<" "<next; + continue; + } + else + { + break; + } + } + + if(cur_val1==NULL||cur_val2==NULL) + { + return ; + } + if(prev_val1!=NULL) + { + prev_val1->next = cur_val2; + } + else + { + *head = cur_val2; + } + if(prev_val2!=NULL) + { + prev_val2->next = cur_val1; + } + else + { + *head = cur_val1; + } + ListNode *temp = cur_val2->next; + cur_val2->next = cur_val1->next; + cur_val1->next = temp; + + } + ListNode* element(ListNode* head,int len) + { + ListNode *t=new ListNode; + t=head; + int idx=0; + while(true) + { + if(idx==len) + { + break; + } + t=t->next; + idx++; + } + + return t; + } + ListNode* swapPairs(ListNode* head) + { + + ListNode *temp = new ListNode; + ListNode *cur = new ListNode; + cur=head; + + temp=head; + int size=0; + while(temp!=NULL) + { + size++; + temp=temp->next; + } + delete temp; + + if(size==1||size==0) + { + return head; + } + + for(int i=0;ival<<" "<val<val,b->val,i); + + + + } + + + } + return head; + + + + } +}; +``` + +https://leetcode.com/problems/swap-nodes-in-pairs/solutions/1774708/c-visual-image-how-links-change-explained-every-step-commented-code/ + +### 마무리 + + ++ fast slow 방식 diff --git a/_posts/2023-12-05-market_analyze.md b/_posts/2023-12-05-market_analyze.md new file mode 100644 index 000000000000..3bd647ee056e --- /dev/null +++ b/_posts/2023-12-05-market_analyze.md @@ -0,0 +1,308 @@ +--- +layout: single +title: "서울 상권 데이터 분석" +categories: EDA,data science,data +tag : [EDA,data science] +toc: true +author_profile: false +sidebar: + nav : "docs" +search: true +--- + +
+
+ +# 서울 상권 데이터 분석 목표 +
+
+ +상권 분석 : 해당 상권이 특정 업종에 적합한지 다각도로 분석하는것을 의미 한다 + + + +20 30 , 30 40 세대 기준으로 상권 분석 할 예정이다 + + + +2019- 2023의 데이터가 존재 하는데 2021-2023까지의 데이터를 사용할 예정이다 + + + +**사용한 서울 상권 데이터** + +
+
+ ++ 생활 인구 + + + 20 30 , 30 40의 유동인구를 파악하는 목적 + +
+
+ ++ 상주 인구 + + + 상권내에 인구가 얼마나 있는지 파악하기 위함 + +
+
+ ++ 아파트 + + + 상권내에 아파트 단지가 몇개 있는지 파악 목적 + +
+
+ ++ 점포 + + + 한국 표준 산업 분류 테이블 기준으로 상권내에 있는 업종을 파악하는게 목적이다 + +
+
+ ++ 접객 시설 + + + 교통이나, 주변에 학교(초중고대) + 상권에 접근성이 좋은지 판단 목적 + +
+
+ ++ 직장 인구 + + + 2030 3040세대의 직장 인구 파악 위함 + +
+
+ ++ 추정 매출 + + + 20 30 3040의 분기면 매출추정 하기 위함 + +
+
+ +203040세대 직장인 인구한테 인기 있는 상권 지역과 203040 유동인구 한테 인기 있는 상권 지역을 분류 하는 목적으로 과제를 진행 하였다 + +
+
+ +사용한 데이터 : 길단위 인구 상권,아파트 상권, 점포 상권, 접객시설 상권, 직장인구 상권, 추정 매출 상권 + + +
+
+ +# 데이터 분석 + +모든 데이터를 나중에 하나로 합치기 위해 각각의 데이터를 상권 구분 코드명과 상권 코드명으로 groupby해주었다 + +
+
+ + +**유동인구수, 직장인구수,추정 매출금액,매출건수는 20세대 30세대 40세대로 각각 나누었다** + +
+
+ + ++ 접객시설 데이터에서 초중고대는 하나의 학교수, 지하철 버스등의 교통시설도 교통시설수, 일반병원 약국수도 종합병원의 수로 합쳐서 처리하였다 + + + ++ 점포 데이터는 다음 그림의 기준을 가지고 점포를 나누어서 처리 하였다 + +
+
+ +![shop image](C:\github_blog\meang123.github.io\images\2023-12-05-market_analyze\image.png) + + +
+
+ +> 모든 데이터는 상권 구분코드 ,상권 코드명을 공통으로 가지고 있기 때문에 이 2가지 attribute로 모든데이터를 통합하고 null data에 대해서는 0으로 처리 하였다 그리고 scale을 정규화 하는 과정은 사이킷런의 minmax sclaer를 사용하였고 결과는 다음과 같다 + + +> 결론적으로 합쳐진 데이터는 각각의 상권 지역에 따른 모든데이터 내용이 통합된 데이터이다 + + +
+
+ +![min_max_sclaer](C:\github_blog\meang123.github.io\images\2023-12-05-market_analyze\image2.png) + +
+
+ + +# knn 모델 결과 + +총상주 인구수, 20,30,40 유동인구수, 20,30,40 직장인구수, 접객시설수, 학교수 ,아파트 단지수,종합병원수를 통합한 새로운 num_data를 만들어주고 이 데이터를 knn에 넣어주었다 그리고 가장 높은 실루엣 점수를 가지는 k를 구하는 과정은 다음과 같다 + + + +![knn score](C:\github_blog\meang123.github.io\images\2023-12-05-market_analyze\image3.png) + + + +하지만 2가지 경우로 분류 하고 싶기 때문에 k=2로 설정 하기로 한다 (유동인구 인기 영역, 직장인 인기 영역) + + + +
+
+ +**다음은 k=2로 knn 진행했을때의 결과 이다** + +
+
+ +![cluster result](C:\github_blog\meang123.github.io\images\2023-12-05-market_analyze\image4.png) + +
+
+ +# cluster = 0 + +
+
+ +![직장인인기영역](C:\github_blog\meang123.github.io\images\2023-12-05-market_analyze\image5.png) + + +![직장인 상권영역](C:\github_blog\meang123.github.io\images\2023-12-05-market_analyze\image6.png) + +
+
+ +# cluster =1 + +
+
+ +![유동인구영역](C:\github_blog\meang123.github.io\images\2023-12-05-market_analyze\image7.png) + +
+
+ +![유동인구상권영역](C:\github_blog\meang123.github.io\images\2023-12-05-market_analyze\image8.png) + +골목 상권이 100%로 나왔는데 합친 데이터에서 결측치 제거 할때나 아니면 합칠때 문제가 있었을것으로 예상한다 + +
+
+ +직장인구와 유동인구의 영역에는 크게 차이가 없이 오락서비스와 카페가 인기가 많은것으로 볼수있다 + + +# 연령별 매출 금액 매출 건수 비교 + +
+
+ +**20대 매출금액** + +![20 매출금액](C:\github_blog\meang123.github.io\images\2023-12-05-market_analyze\image9.png) + +
+
+ + +**30대 매출금액** + + +![30 매출금액](C:\github_blog\meang123.github.io\images\2023-12-05-market_analyze\image10.png) + +
+
+ + +
+
+ + + +**40대 매출금액** +
+
+ +![40 매출금액](C:\github_blog\meang123.github.io\images\2023-12-05-market_analyze\image11.png) +
+
+ + +**20대 매출건수** + + +![20 매출건수](C:\github_blog\meang123.github.io\images\2023-12-05-market_analyze\image12.png) +
+
+ + +**30대 매출건수** + + +![30매출건수](C:\github_blog\meang123.github.io\images\2023-12-05-market_analyze\image13.png) + +
+
+ + +**40대 매출건수** + + +![40매출건수](C:\github_blog\meang123.github.io\images\2023-12-05-market_analyze\image14.png) + + +
+
+ +기본적으로 유동인구 보다 직장인구 매출금액 건수의 큰 이상치가 많은것으로 보아서 직장인구 상권 영역이 더 큰 매출과 건수를 가지는것으로 볼수있을것 같다 그리고 20<30<40 순으로 매출금액 차이가 있음을 알수있다 + + + +
+
+ +# 정리 + +
+
+ ++ 직장인구 인기 영역에서는 종합 병원수 교통 시설, 접객 시설이 많고 아파트 단지수 ,학교수, 203040유동인구,총상주인구가 부족한 편인 영역임을 알수있다 + + + + + ++ 유동인구 인기영역은 반대로 아파트 단지수와 학교수,총상주 인구가 많은 편이고 주변에 종합병원수,교통시설수,접객시설이 비교적 부족한 편임을 알수가 있다 + + + + +
+
+ + +# 데이터 분석 코드와 knn모델 코드 + +다음 깃헙 주소에서 전체 코드를 볼수있다 + + + +https://github.com/meang123/market_analyze + +
+
+ +# 참고 링크 + +https://github.com/chenni0531/data-seoul-market-analysis + +
+
+ diff --git a/google72d77fcd341117e3.html b/google72d77fcd341117e3.html new file mode 100644 index 000000000000..bcf585a96e28 --- /dev/null +++ b/google72d77fcd341117e3.html @@ -0,0 +1 @@ +google-site-verification: google72d77fcd341117e3.html \ No newline at end of file diff --git "a/images/2022-04-26-process_syncri/\343\205\201\343\204\264\343\205\207\343\204\271\354\227\206\353\212\224 \352\267\270\353\246\274.png" "b/images/2022-04-26-process_syncri/\343\205\201\343\204\264\343\205\207\343\204\271\354\227\206\353\212\224 \352\267\270\353\246\274.png" new file mode 100644 index 000000000000..50ba2efe53d9 Binary files /dev/null and "b/images/2022-04-26-process_syncri/\343\205\201\343\204\264\343\205\207\343\204\271\354\227\206\353\212\224 \352\267\270\353\246\274.png" differ diff --git "a/images/2022-04-26-process_syncri/\354\240\234\353\252\251 dfdfd\352\267\270\353\246\274.png" "b/images/2022-04-26-process_syncri/\354\240\234\353\252\251 dfdfd\352\267\270\353\246\274.png" new file mode 100644 index 000000000000..d3d1f7a03ce3 Binary files /dev/null and "b/images/2022-04-26-process_syncri/\354\240\234\353\252\251 dfdfd\352\267\270\353\246\274.png" differ diff --git "a/images/2022-04-26-process_syncri/\355\231\224\353\251\264 \354\272\241\354\262\230 2022-07-19 162618.png" "b/images/2022-04-26-process_syncri/\355\231\224\353\251\264 \354\272\241\354\262\230 2022-07-19 162618.png" new file mode 100644 index 000000000000..c72a70b76fb7 Binary files /dev/null and "b/images/2022-04-26-process_syncri/\355\231\224\353\251\264 \354\272\241\354\262\230 2022-07-19 162618.png" differ diff --git "a/images/2022-04-26-process_syncri/\355\231\224\353\251\264 \354\272\241\354\262\230 2022-07-21 155003.png" "b/images/2022-04-26-process_syncri/\355\231\224\353\251\264 \354\272\241\354\262\230 2022-07-21 155003.png" new file mode 100644 index 000000000000..16f0858568c8 Binary files /dev/null and "b/images/2022-04-26-process_syncri/\355\231\224\353\251\264 \354\272\241\354\262\230 2022-07-21 155003.png" differ diff --git a/images/2022-07-09-Basic_of_OS/blog_2.png b/images/2022-07-09-Basic_of_OS/blog_2.png new file mode 100644 index 000000000000..8ca8d51cb693 Binary files /dev/null and b/images/2022-07-09-Basic_of_OS/blog_2.png differ diff --git "a/images/2022-07-09-Basic_of_OS/\355\231\224\353\251\264 \354\272\241\354\262\230 2022-07-09 194116.png" "b/images/2022-07-09-Basic_of_OS/\355\231\224\353\251\264 \354\272\241\354\262\230 2022-07-09 194116.png" new file mode 100644 index 000000000000..7c191b3981be Binary files /dev/null and "b/images/2022-07-09-Basic_of_OS/\355\231\224\353\251\264 \354\272\241\354\262\230 2022-07-09 194116.png" differ diff --git a/images/2022-07-10-Process/IPC@.png b/images/2022-07-10-Process/IPC@.png new file mode 100644 index 000000000000..a69b907b7280 Binary files /dev/null and b/images/2022-07-10-Process/IPC@.png differ diff --git a/images/2022-07-10-Process/process_state.png b/images/2022-07-10-Process/process_state.png new file mode 100644 index 000000000000..e18da1579a50 Binary files /dev/null and b/images/2022-07-10-Process/process_state.png differ diff --git "a/images/2022-07-13-Treads/\353\213\244\354\232\264\353\241\234\353\223\234.png" "b/images/2022-07-13-Treads/\353\213\244\354\232\264\353\241\234\353\223\234.png" new file mode 100644 index 000000000000..516222677a06 Binary files /dev/null and "b/images/2022-07-13-Treads/\353\213\244\354\232\264\353\241\234\353\223\234.png" differ diff --git "a/images/2022-07-13-Treads/\354\240\234\353\252\251 \354\227\206\353\212\224 \352\267\270\353\246\274.png" "b/images/2022-07-13-Treads/\354\240\234\353\252\251 \354\227\206\353\212\224 \352\267\270\353\246\274.png" new file mode 100644 index 000000000000..168891ed1a28 Binary files /dev/null and "b/images/2022-07-13-Treads/\354\240\234\353\252\251 \354\227\206\353\212\224 \352\267\270\353\246\274.png" differ diff --git a/images/2022-07-14-Scheduling/dfg.png b/images/2022-07-14-Scheduling/dfg.png new file mode 100644 index 000000000000..68d141e47d17 Binary files /dev/null and b/images/2022-07-14-Scheduling/dfg.png differ diff --git a/images/2022-07-14-Scheduling/ffff.png b/images/2022-07-14-Scheduling/ffff.png new file mode 100644 index 000000000000..d3a978621c13 Binary files /dev/null and b/images/2022-07-14-Scheduling/ffff.png differ diff --git "a/images/2022-07-21-multi_thread_syn/\355\231\224\353\251\264 \354\272\241\354\262\230 2022-07-21 201055.png" "b/images/2022-07-21-multi_thread_syn/\355\231\224\353\251\264 \354\272\241\354\262\230 2022-07-21 201055.png" new file mode 100644 index 000000000000..d5acedb148a6 Binary files /dev/null and "b/images/2022-07-21-multi_thread_syn/\355\231\224\353\251\264 \354\272\241\354\262\230 2022-07-21 201055.png" differ diff --git "a/images/2022-07-21-multi_thread_syn/\355\231\224\353\251\264 \354\272\241\354\262\230 2022-07-21 203944.png" "b/images/2022-07-21-multi_thread_syn/\355\231\224\353\251\264 \354\272\241\354\262\230 2022-07-21 203944.png" new file mode 100644 index 000000000000..effdf4e3f0a4 Binary files /dev/null and "b/images/2022-07-21-multi_thread_syn/\355\231\224\353\251\264 \354\272\241\354\262\230 2022-07-21 203944.png" differ diff --git a/images/2023-12-05-market_analyze/image.png b/images/2023-12-05-market_analyze/image.png new file mode 100644 index 000000000000..0a57409ef027 Binary files /dev/null and b/images/2023-12-05-market_analyze/image.png differ diff --git a/images/2023-12-05-market_analyze/image10.png b/images/2023-12-05-market_analyze/image10.png new file mode 100644 index 000000000000..51d711bb318d Binary files /dev/null and b/images/2023-12-05-market_analyze/image10.png differ diff --git a/images/2023-12-05-market_analyze/image11.png b/images/2023-12-05-market_analyze/image11.png new file mode 100644 index 000000000000..9af8b25a6fa2 Binary files /dev/null and b/images/2023-12-05-market_analyze/image11.png differ diff --git a/images/2023-12-05-market_analyze/image12.png b/images/2023-12-05-market_analyze/image12.png new file mode 100644 index 000000000000..9e70c0dbb871 Binary files /dev/null and b/images/2023-12-05-market_analyze/image12.png differ diff --git a/images/2023-12-05-market_analyze/image13.png b/images/2023-12-05-market_analyze/image13.png new file mode 100644 index 000000000000..cd5cb1d8274d Binary files /dev/null and b/images/2023-12-05-market_analyze/image13.png differ diff --git a/images/2023-12-05-market_analyze/image14.png b/images/2023-12-05-market_analyze/image14.png new file mode 100644 index 000000000000..6114a5f1bbce Binary files /dev/null and b/images/2023-12-05-market_analyze/image14.png differ diff --git a/images/2023-12-05-market_analyze/image2.png b/images/2023-12-05-market_analyze/image2.png new file mode 100644 index 000000000000..b050527202a0 Binary files /dev/null and b/images/2023-12-05-market_analyze/image2.png differ diff --git a/images/2023-12-05-market_analyze/image3.png b/images/2023-12-05-market_analyze/image3.png new file mode 100644 index 000000000000..dbe5ff8c7765 Binary files /dev/null and b/images/2023-12-05-market_analyze/image3.png differ diff --git a/images/2023-12-05-market_analyze/image4.png b/images/2023-12-05-market_analyze/image4.png new file mode 100644 index 000000000000..d45e7c64cd71 Binary files /dev/null and b/images/2023-12-05-market_analyze/image4.png differ diff --git a/images/2023-12-05-market_analyze/image5.png b/images/2023-12-05-market_analyze/image5.png new file mode 100644 index 000000000000..40b56a39b019 Binary files /dev/null and b/images/2023-12-05-market_analyze/image5.png differ diff --git a/images/2023-12-05-market_analyze/image6.png b/images/2023-12-05-market_analyze/image6.png new file mode 100644 index 000000000000..741909266fec Binary files /dev/null and b/images/2023-12-05-market_analyze/image6.png differ diff --git a/images/2023-12-05-market_analyze/image7.png b/images/2023-12-05-market_analyze/image7.png new file mode 100644 index 000000000000..f3a92af070ab Binary files /dev/null and b/images/2023-12-05-market_analyze/image7.png differ diff --git a/images/2023-12-05-market_analyze/image8.png b/images/2023-12-05-market_analyze/image8.png new file mode 100644 index 000000000000..07aba2c8eb7e Binary files /dev/null and b/images/2023-12-05-market_analyze/image8.png differ diff --git a/images/2023-12-05-market_analyze/image9.png b/images/2023-12-05-market_analyze/image9.png new file mode 100644 index 000000000000..3578e9ecf205 Binary files /dev/null and b/images/2023-12-05-market_analyze/image9.png differ diff --git a/pdfs/Assignment_compiler_5.pdf b/pdfs/Assignment_compiler_5.pdf new file mode 100644 index 000000000000..c92a73be35b3 Binary files /dev/null and b/pdfs/Assignment_compiler_5.pdf differ diff --git a/pdfs/Bottom_up_Parsing.pdf b/pdfs/Bottom_up_Parsing.pdf new file mode 100644 index 000000000000..0d5490e585e2 Binary files /dev/null and b/pdfs/Bottom_up_Parsing.pdf differ diff --git a/pdfs/Function_Semantic.pdf b/pdfs/Function_Semantic.pdf new file mode 100644 index 000000000000..ea1fb240a0f9 Binary files /dev/null and b/pdfs/Function_Semantic.pdf differ diff --git a/pdfs/LEX_Yacc.pdf b/pdfs/LEX_Yacc.pdf new file mode 100644 index 000000000000..b5de6d5a5bb7 Binary files /dev/null and b/pdfs/LEX_Yacc.pdf differ diff --git a/pdfs/Lexical.pdf b/pdfs/Lexical.pdf new file mode 100644 index 000000000000..c4576dafb39c Binary files /dev/null and b/pdfs/Lexical.pdf differ diff --git a/pdfs/OS_Process.pdf b/pdfs/OS_Process.pdf new file mode 100644 index 000000000000..cc29ee0a7b36 Binary files /dev/null and b/pdfs/OS_Process.pdf differ diff --git a/pdfs/Process_Syn.pdf b/pdfs/Process_Syn.pdf new file mode 100644 index 000000000000..1a5b7ddbd915 Binary files /dev/null and b/pdfs/Process_Syn.pdf differ diff --git a/pdfs/Semantic.pdf b/pdfs/Semantic.pdf new file mode 100644 index 000000000000..611bbd95b597 Binary files /dev/null and b/pdfs/Semantic.pdf differ diff --git a/pdfs/Syntax.pdf b/pdfs/Syntax.pdf new file mode 100644 index 000000000000..572c38eca017 Binary files /dev/null and b/pdfs/Syntax.pdf differ diff --git a/pdfs/Syntax_analysis_Top_down_parsing.pdf b/pdfs/Syntax_analysis_Top_down_parsing.pdf new file mode 100644 index 000000000000..5fd84d9bbc38 Binary files /dev/null and b/pdfs/Syntax_analysis_Top_down_parsing.pdf differ diff --git a/pdfs/Thread.pdf b/pdfs/Thread.pdf new file mode 100644 index 000000000000..d63ddad589be Binary files /dev/null and b/pdfs/Thread.pdf differ diff --git a/pdfs/Top_down_Parsing.pdf b/pdfs/Top_down_Parsing.pdf new file mode 100644 index 000000000000..2446c3ef4d35 Binary files /dev/null and b/pdfs/Top_down_Parsing.pdf differ diff --git a/pdfs/TypeChecker.pdf b/pdfs/TypeChecker.pdf new file mode 100644 index 000000000000..f7b33fcaeb0b Binary files /dev/null and b/pdfs/TypeChecker.pdf differ diff --git a/pdfs/basicOS.pdf b/pdfs/basicOS.pdf new file mode 100644 index 000000000000..f81097966755 Binary files /dev/null and b/pdfs/basicOS.pdf differ diff --git a/pdfs/clite_compiler.pdf b/pdfs/clite_compiler.pdf new file mode 100644 index 000000000000..ef31441f4346 Binary files /dev/null and b/pdfs/clite_compiler.pdf differ diff --git a/pdfs/compiler_onenote.pdf b/pdfs/compiler_onenote.pdf new file mode 100644 index 000000000000..8b2601eab2de Binary files /dev/null and b/pdfs/compiler_onenote.pdf differ diff --git a/pdfs/scheduling.pdf b/pdfs/scheduling.pdf new file mode 100644 index 000000000000..67d47ae57abb Binary files /dev/null and b/pdfs/scheduling.pdf differ diff --git a/pdfs/syntax2.pdf b/pdfs/syntax2.pdf new file mode 100644 index 000000000000..15e283eb458a Binary files /dev/null and b/pdfs/syntax2.pdf differ